Przeglądaj źródła

Add certificate to streams database model

jbowring 1 rok temu
rodzic
commit
cd80cc8e4d

+ 86 - 13
backend/internal/stream.js

@@ -1,10 +1,12 @@
-const _                = require('lodash');
-const error            = require('../lib/error');
-const utils            = require('../lib/utils');
-const streamModel      = require('../models/stream');
-const internalNginx    = require('./nginx');
-const internalAuditLog = require('./audit-log');
-const {castJsonIfNeed} = require('../lib/helpers');
+const _                   = require('lodash');
+const error               = require('../lib/error');
+const utils               = require('../lib/utils');
+const streamModel         = require('../models/stream');
+const internalNginx       = require('./nginx');
+const internalAuditLog    = require('./audit-log');
+const internalCertificate = require('./certificate');
+const internalHost        = require('./host');
+const {castJsonIfNeed}    = require('../lib/helpers');
 
 function omissions () {
 	return ['is_deleted'];
@@ -18,6 +20,12 @@ const internalStream = {
 	 * @returns {Promise}
 	 */
 	create: (access, data) => {
+		let create_certificate = data.certificate_id === 'new';
+
+		if (create_certificate) {
+			delete data.certificate_id;
+		}
+
 		return access.can('streams:create', data)
 			.then((/*access_data*/) => {
 				// TODO: At this point the existing ports should have been checked
@@ -27,11 +35,40 @@ const internalStream = {
 					data.meta = {};
 				}
 
+				let data_no_domains = structuredClone(data);
+
+				// streams aren't routed by domain name so don't store domain names in the DB
+				delete data_no_domains.domain_names;
+
 				return streamModel
 					.query()
-					.insertAndFetch(data)
+					.insertAndFetch(data_no_domains)
 					.then(utils.omitRow(omissions()));
 			})
+			.then((row) => {
+				if (create_certificate) {
+					return internalCertificate.createQuickCertificate(access, data)
+						.then((cert) => {
+							// update host with cert id
+							return internalStream.update(access, {
+								id:             row.id,
+								certificate_id: cert.id
+							});
+						})
+						.then(() => {
+							return row;
+						});
+				} else {
+					return row;
+				}
+			})
+			.then((row) => {
+				// re-fetch with cert
+				return internalStream.get(access, {
+					id:     row.id,
+					expand: ['certificate', 'owner']
+				});
+			})
 			.then((row) => {
 				// Configure nginx
 				return internalNginx.configure(streamModel, 'stream', row)
@@ -60,6 +97,12 @@ const internalStream = {
 	 * @return {Promise}
 	 */
 	update: (access, data) => {
+		let create_certificate = data.certificate_id === 'new';
+
+		if (create_certificate) {
+			delete data.certificate_id;
+		}
+
 		return access.can('streams:update', data.id)
 			.then((/*access_data*/) => {
 				// TODO: at this point the existing streams should have been checked
@@ -71,6 +114,28 @@ const internalStream = {
 					throw new error.InternalValidationError('Stream could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
 				}
 
+				if (create_certificate) {
+					return internalCertificate.createQuickCertificate(access, {
+						domain_names: data.domain_names || row.domain_names,
+						meta:         _.assign({}, row.meta, data.meta)
+					})
+						.then((cert) => {
+							// update host with cert id
+							data.certificate_id = cert.id;
+						})
+						.then(() => {
+							return row;
+						});
+				} else {
+					return row;
+				}
+			})
+			.then((row) => {
+				// Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here.
+				data = _.assign({}, {
+					domain_names: row.domain_names
+				}, data);
+
 				return streamModel
 					.query()
 					.patchAndFetchById(row.id, data)
@@ -115,7 +180,7 @@ const internalStream = {
 					.query()
 					.where('is_deleted', 0)
 					.andWhere('id', data.id)
-					.allowGraph('[owner]')
+					.allowGraph('[owner,certificate]')
 					.first();
 
 				if (access_data.permission_visibility !== 'all') {
@@ -132,6 +197,7 @@ const internalStream = {
 				if (!row || !row.id) {
 					throw new error.ItemNotFoundError(data.id);
 				}
+				row = internalHost.cleanRowCertificateMeta(row);
 				// Custom omissions
 				if (typeof data.omit !== 'undefined' && data.omit !== null) {
 					row = _.omit(row, data.omit);
@@ -197,14 +263,14 @@ const internalStream = {
 			.then(() => {
 				return internalStream.get(access, {
 					id:     data.id,
-					expand: ['owner']
+					expand: ['certificate', 'owner']
 				});
 			})
 			.then((row) => {
 				if (!row || !row.id) {
 					throw new error.ItemNotFoundError(data.id);
 				} else if (row.enabled) {
-					throw new error.ValidationError('Host is already enabled');
+					throw new error.ValidationError('Stream is already enabled');
 				}
 
 				row.enabled = 1;
@@ -250,7 +316,7 @@ const internalStream = {
 				if (!row || !row.id) {
 					throw new error.ItemNotFoundError(data.id);
 				} else if (!row.enabled) {
-					throw new error.ValidationError('Host is already disabled');
+					throw new error.ValidationError('Stream is already disabled');
 				}
 
 				row.enabled = 0;
@@ -298,7 +364,7 @@ const internalStream = {
 					.query()
 					.where('is_deleted', 0)
 					.groupBy('id')
-					.allowGraph('[owner]')
+					.allowGraph('[owner,certificate]')
 					.orderByRaw('CAST(incoming_port AS INTEGER) ASC');
 
 				if (access_data.permission_visibility !== 'all') {
@@ -317,6 +383,13 @@ const internalStream = {
 				}
 
 				return query.then(utils.omitRows(omissions()));
+			})
+			.then((rows) => {
+				if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) {
+					return internalHost.cleanAllRowsCertificateMeta(rows);
+				}
+
+				return rows;
 			});
 	},
 

+ 38 - 0
backend/migrations/20240427161436_stream_ssl.js

@@ -0,0 +1,38 @@
+const migrate_name = 'stream_ssl';
+const logger       = require('../logger').migrate;
+
+/**
+ * Migrate
+ *
+ * @see http://knexjs.org/#Schema
+ *
+ * @param   {Object} knex
+ * @returns {Promise}
+ */
+exports.up = function (knex) {
+	logger.info('[' + migrate_name + '] Migrating Up...');
+
+	return knex.schema.table('stream', (table) => {
+		table.integer('certificate_id').notNull().unsigned().defaultTo(0);
+	})
+		.then(function () {
+			logger.info('[' + migrate_name + '] stream Table altered');
+		});
+};
+
+/**
+ * Undo Migrate
+ *
+ * @param   {Object} knex
+ * @returns {Promise}
+ */
+exports.down = function (knex) {
+	logger.info('[' + migrate_name + '] Migrating Down...');
+
+	return knex.schema.table('stream', (table) => {
+		table.dropColumn('certificate_id');
+	})
+		.then(function () {
+			logger.info('[' + migrate_name + '] stream Table altered');
+		});
+};

+ 17 - 8
backend/models/stream.js

@@ -1,11 +1,9 @@
-// Objection Docs:
-// http://vincit.github.io/objection.js/
-
-const db      = require('../db');
-const helpers = require('../lib/helpers');
-const Model   = require('objection').Model;
-const User    = require('./user');
-const now     = require('./now_helper');
+const Model       = require('objection').Model;
+const db          = require('../db');
+const helpers     = require('../lib/helpers');
+const User        = require('./user');
+const Certificate = require('./certificate');
+const now         = require('./now_helper');
 
 Model.knex(db);
 
@@ -64,6 +62,17 @@ class Stream extends Model {
 				modify: function (qb) {
 					qb.where('user.is_deleted', 0);
 				}
+			},
+			certificate: {
+				relation:   Model.HasOneRelation,
+				modelClass: Certificate,
+				join:       {
+					from: 'stream.certificate_id',
+					to:   'certificate.id'
+				},
+				modify: function (qb) {
+					qb.where('certificate.is_deleted', 0);
+				}
 			}
 		};
 	}

+ 1 - 1
frontend/js/app/nginx/stream/main.js

@@ -88,7 +88,7 @@ module.exports = Mn.View.extend({
     onRender: function () {
         let view = this;
 
-        view.fetch(['owner'])
+        view.fetch(['owner', 'certificate'])
             .then(response => {
                 if (!view.isDestroyed()) {
                     if (response && response.length) {