From 981d5a199f88dfcd82681d5136a7811072cb72f3 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Thu, 3 Jan 2019 20:27:43 +1000 Subject: [PATCH] Initial work for disabling hosts --- src/backend/internal/proxy-host.js | 105 +++++++++++++++- .../migrations/20190104035154_disabled.js | 57 +++++++++ src/backend/routes/api/nginx/proxy_hosts.js | 52 +++++++- src/backend/schema/definitions.json | 5 + src/backend/schema/endpoints/proxy-hosts.json | 40 ++++++ src/backend/templates/proxy_host.conf | 2 + src/frontend/js/app/api.js | 116 ++++++++++++++---- src/frontend/js/app/nginx/proxy/list/item.ejs | 6 +- src/frontend/js/app/nginx/proxy/list/item.js | 13 ++ src/frontend/js/i18n/messages.json | 2 + src/frontend/js/models/proxy-host.js | 1 + 11 files changed, 368 insertions(+), 31 deletions(-) create mode 100644 src/backend/migrations/20190104035154_disabled.js diff --git a/src/backend/internal/proxy-host.js b/src/backend/internal/proxy-host.js index 1946427b..5eabdebf 100644 --- a/src/backend/internal/proxy-host.js +++ b/src/backend/internal/proxy-host.js @@ -104,7 +104,7 @@ const internalProxyHost = { /** * @param {Access} access * @param {Object} data - * @param {Integer} data.id + * @param {Number} data.id * @return {Promise} */ update: (access, data) => { @@ -192,7 +192,7 @@ const internalProxyHost = { return internalNginx.configure(proxyHostModel, 'proxy_host', row) .then(new_meta => { row.meta = new_meta; - row = internalHost.cleanRowCertificateMeta(row); + row = internalHost.cleanRowCertificateMeta(row); return _.omit(row, omissions()); }); }); @@ -202,7 +202,7 @@ const internalProxyHost = { /** * @param {Access} access * @param {Object} data - * @param {Integer} data.id + * @param {Number} data.id * @param {Array} [data.expand] * @param {Array} [data.omit] * @return {Promise} @@ -249,7 +249,7 @@ const internalProxyHost = { /** * @param {Access} access * @param {Object} data - * @param {Integer} data.id + * @param {Number} data.id * @param {String} [data.reason] * @returns {Promise} */ @@ -291,6 +291,101 @@ const internalProxyHost = { }); }, + /** + * @param {Access} access + * @param {Object} data + * @param {Number} data.id + * @param {String} [data.reason] + * @returns {Promise} + */ + enable: (access, data) => { + return access.can('proxy_hosts:update', data.id) + .then(() => { + return internalProxyHost.get(access, {id: data.id}); + }) + .then(row => { + if (!row) { + throw new error.ItemNotFoundError(data.id); + } else if (row.enabled) { + throw new error.ValidationError('Host is already enabled'); + } + + row.enabled = 1; + + return proxyHostModel + .query() + .where('id', row.id) + .patch({ + enabled: 1 + }) + .then(() => { + // Configure nginx + return internalNginx.configure(proxyHostModel, 'proxy_host', row); + }) + .then(() => { + // Add to audit log + return internalAuditLog.add(access, { + action: 'enabled', + object_type: 'proxy-host', + object_id: row.id, + meta: _.omit(row, omissions()) + }); + }); + }) + .then(() => { + return true; + }); + }, + + /** + * @param {Access} access + * @param {Object} data + * @param {Number} data.id + * @param {String} [data.reason] + * @returns {Promise} + */ + disable: (access, data) => { + return access.can('proxy_hosts:update', data.id) + .then(() => { + return internalProxyHost.get(access, {id: data.id}); + }) + .then(row => { + if (!row) { + throw new error.ItemNotFoundError(data.id); + } else if (!row.enabled) { + throw new error.ValidationError('Host is already disabled'); + } + + row.enabled = 0; + + return proxyHostModel + .query() + .where('id', row.id) + .patch({ + enabled: 0 + }) + .then(() => { + // Delete Nginx Config + return internalNginx.deleteConfig('proxy_host', row) + .then(() => { + return internalNginx.reload(); + }); + }) + .then(() => { + // Add to audit log + return internalAuditLog.add(access, { + action: 'disabled', + object_type: 'proxy-host', + object_id: row.id, + meta: _.omit(row, omissions()) + }); + }); + }) + .then(() => { + return true; + }); + }, + /** * All Hosts * @@ -339,7 +434,7 @@ const internalProxyHost = { /** * Report use * - * @param {Integer} user_id + * @param {Number} user_id * @param {String} visibility * @returns {Promise} */ diff --git a/src/backend/migrations/20190104035154_disabled.js b/src/backend/migrations/20190104035154_disabled.js new file mode 100644 index 00000000..308e219d --- /dev/null +++ b/src/backend/migrations/20190104035154_disabled.js @@ -0,0 +1,57 @@ +'use strict'; + +const migrate_name = 'disabled'; +const logger = require('../logger').migrate; + +/** + * Migrate + * + * @see http://knexjs.org/#Schema + * + * @param {Object} knex + * @param {Promise} Promise + * @returns {Promise} + */ +exports.up = function (knex/*, Promise*/) { + logger.info('[' + migrate_name + '] Migrating Up...'); + + return knex.schema.table('proxy_host', function (proxy_host) { + proxy_host.integer('enabled').notNull().unsigned().defaultTo(1); + }) + .then(() => { + logger.info('[' + migrate_name + '] proxy_host Table altered'); + + return knex.schema.table('redirection_host', function (redirection_host) { + redirection_host.integer('enabled').notNull().unsigned().defaultTo(1); + }); + }) + .then(() => { + logger.info('[' + migrate_name + '] redirection_host Table altered'); + + return knex.schema.table('dead_host', function (dead_host) { + dead_host.integer('enabled').notNull().unsigned().defaultTo(1); + }); + }) + .then(() => { + logger.info('[' + migrate_name + '] dead_host Table altered'); + + return knex.schema.table('stream', function (stream) { + stream.integer('enabled').notNull().unsigned().defaultTo(1); + }); + }) + .then(() => { + logger.info('[' + migrate_name + '] stream Table altered'); + }); +}; + +/** + * Undo Migrate + * + * @param {Object} knex + * @param {Promise} Promise + * @returns {Promise} + */ +exports.down = function (knex, Promise) { + logger.warn('[' + migrate_name + '] You can\'t migrate down this one.'); + return Promise.resolve(true); +}; diff --git a/src/backend/routes/api/nginx/proxy_hosts.js b/src/backend/routes/api/nginx/proxy_hosts.js index ad69f003..718f135e 100644 --- a/src/backend/routes/api/nginx/proxy_hosts.js +++ b/src/backend/routes/api/nginx/proxy_hosts.js @@ -20,7 +20,7 @@ router .options((req, res) => { res.sendStatus(204); }) - .all(jwtdecode()) // preferred so it doesn't apply to nonexistent routes + .all(jwtdecode()) /** * GET /api/nginx/proxy-hosts @@ -79,7 +79,7 @@ router .options((req, res) => { res.sendStatus(204); }) - .all(jwtdecode()) // preferred so it doesn't apply to nonexistent routes + .all(jwtdecode()) /** * GET /api/nginx/proxy-hosts/123 @@ -147,4 +147,52 @@ router .catch(next); }); +/** + * Enable proxy-host + * + * /api/nginx/proxy-hosts/123/enable + */ +router + .route('/:host_id/enable') + .options((req, res) => { + res.sendStatus(204); + }) + .all(jwtdecode()) + + /** + * POST /api/nginx/proxy-hosts/123/enable + */ + .post((req, res, next) => { + internalProxyHost.enable(res.locals.access, {id: parseInt(req.params.host_id, 10)}) + .then(result => { + res.status(200) + .send(result); + }) + .catch(next); + }); + +/** + * Disable proxy-host + * + * /api/nginx/proxy-hosts/123/disable + */ +router + .route('/:host_id/disable') + .options((req, res) => { + res.sendStatus(204); + }) + .all(jwtdecode()) + + /** + * POST /api/nginx/proxy-hosts/123/disable + */ + .post((req, res, next) => { + internalProxyHost.disable(res.locals.access, {id: parseInt(req.params.host_id, 10)}) + .then(result => { + res.status(200) + .send(result); + }) + .catch(next); + }); + module.exports = router; diff --git a/src/backend/schema/definitions.json b/src/backend/schema/definitions.json index 8320b3d2..66548113 100644 --- a/src/backend/schema/definitions.json +++ b/src/backend/schema/definitions.json @@ -172,6 +172,11 @@ "pattern": "^(?:\\*\\.)?(?:[^.*]+\\.?)+[^.]$" } }, + "enabled": { + "description": "Is Enabled", + "example": true, + "type": "boolean" + }, "ssl_enabled": { "description": "Is SSL Enabled", "example": true, diff --git a/src/backend/schema/endpoints/proxy-hosts.json b/src/backend/schema/endpoints/proxy-hosts.json index ae8a1a52..9e033da2 100644 --- a/src/backend/schema/endpoints/proxy-hosts.json +++ b/src/backend/schema/endpoints/proxy-hosts.json @@ -58,6 +58,9 @@ "advanced_config": { "type": "string" }, + "enabled": { + "$ref": "../definitions.json#/definitions/enabled" + }, "meta": { "type": "object" } @@ -108,6 +111,9 @@ "advanced_config": { "$ref": "#/definitions/advanced_config" }, + "enabled": { + "$ref": "#/definitions/enabled" + }, "meta": { "$ref": "#/definitions/meta" } @@ -186,6 +192,9 @@ "advanced_config": { "$ref": "#/definitions/advanced_config" }, + "enabled": { + "$ref": "#/definitions/enabled" + }, "meta": { "$ref": "#/definitions/meta" } @@ -247,6 +256,9 @@ "advanced_config": { "$ref": "#/definitions/advanced_config" }, + "enabled": { + "$ref": "#/definitions/enabled" + }, "meta": { "$ref": "#/definitions/meta" } @@ -271,6 +283,34 @@ "targetSchema": { "type": "boolean" } + }, + { + "title": "Enable", + "description": "Enables a existing Proxy Host", + "href": "/nginx/proxy-hosts/{definitions.identity.example}/enable", + "access": "private", + "method": "POST", + "rel": "update", + "http_header": { + "$ref": "../examples.json#/definitions/auth_header" + }, + "targetSchema": { + "type": "boolean" + } + }, + { + "title": "Disable", + "description": "Disables a existing Proxy Host", + "href": "/nginx/proxy-hosts/{definitions.identity.example}/disable", + "access": "private", + "method": "POST", + "rel": "update", + "http_header": { + "$ref": "../examples.json#/definitions/auth_header" + }, + "targetSchema": { + "type": "boolean" + } } ] } diff --git a/src/backend/templates/proxy_host.conf b/src/backend/templates/proxy_host.conf index af89834d..3f3b80bb 100644 --- a/src/backend/templates/proxy_host.conf +++ b/src/backend/templates/proxy_host.conf @@ -1,5 +1,6 @@ {% include "_header_comment.conf" %} +{% if enabled %} server { set $forward_scheme {{ forward_scheme }}; set $server "{{ forward_host }}"; @@ -33,3 +34,4 @@ server { include conf.d/include/proxy.conf; } } +{% endif %} diff --git a/src/frontend/js/app/api.js b/src/frontend/js/app/api.js index 76ec5c96..2da64149 100644 --- a/src/frontend/js/app/api.js +++ b/src/frontend/js/app/api.js @@ -7,7 +7,7 @@ const Tokens = require('./tokens'); /** * @param {String} message * @param {*} debug - * @param {Integer} code + * @param {Number} code * @constructor */ const ApiError = function (message, debug, code) { @@ -129,7 +129,7 @@ function getAllObjects (path, expand, query) { } /** - * @param {String} path + * @param {String} path * @param {FormData} form_data * @returns {Promise} */ @@ -241,7 +241,7 @@ module.exports = { /** * @param {Object} data - * @param {Integer} data.id + * @param {Number} data.id * @returns {Promise} */ update: function (data) { @@ -251,7 +251,7 @@ module.exports = { }, /** - * @param {Integer} id + * @param {Number} id * @returns {Promise} */ delete: function (id) { @@ -260,7 +260,7 @@ module.exports = { /** * - * @param {Integer} id + * @param {Number} id * @param {Object} auth * @returns {Promise} */ @@ -269,7 +269,7 @@ module.exports = { }, /** - * @param {Integer} id + * @param {Number} id * @returns {Promise} */ loginAs: function (id) { @@ -278,7 +278,7 @@ module.exports = { /** * - * @param {Integer} id + * @param {Number} id * @param {Object} perms * @returns {Promise} */ @@ -308,7 +308,7 @@ module.exports = { /** * @param {Object} data - * @param {Integer} data.id + * @param {Number} data.id * @returns {Promise} */ update: function (data) { @@ -318,11 +318,35 @@ module.exports = { }, /** - * @param {Integer} id + * @param {Number} id * @returns {Promise} */ delete: function (id) { return fetch('delete', 'nginx/proxy-hosts/' + id); + }, + + /** + * @param {Number} id + * @returns {Promise} + */ + get: function (id) { + return fetch('get', 'nginx/proxy-hosts/' + id); + }, + + /** + * @param {Number} id + * @returns {Promise} + */ + enable: function (id) { + return fetch('post', 'nginx/proxy-hosts/' + id + '/enable'); + }, + + /** + * @param {Number} id + * @returns {Promise} + */ + disable: function (id) { + return fetch('post', 'nginx/proxy-hosts/' + id + '/disable'); } }, @@ -345,7 +369,7 @@ module.exports = { /** * @param {Object} data - * @param {Integer} data.id + * @param {Number} data.id * @returns {Promise} */ update: function (data) { @@ -355,7 +379,7 @@ module.exports = { }, /** - * @param {Integer} id + * @param {Number} id * @returns {Promise} */ delete: function (id) { @@ -363,12 +387,28 @@ module.exports = { }, /** - * @param {Integer} id + * @param {Number} id * @param {FormData} form_data * @params {Promise} */ setCerts: function (id, form_data) { return FileUpload('nginx/redirection-hosts/' + id + '/certificates', form_data); + }, + + /** + * @param {Number} id + * @returns {Promise} + */ + enable: function (id) { + return fetch('post', 'nginx/redirection-hosts/' + id + '/enable'); + }, + + /** + * @param {Number} id + * @returns {Promise} + */ + disable: function (id) { + return fetch('post', 'nginx/redirection-hosts/' + id + '/disable'); } }, @@ -391,7 +431,7 @@ module.exports = { /** * @param {Object} data - * @param {Integer} data.id + * @param {Number} data.id * @returns {Promise} */ update: function (data) { @@ -401,11 +441,27 @@ module.exports = { }, /** - * @param {Integer} id + * @param {Number} id * @returns {Promise} */ delete: function (id) { return fetch('delete', 'nginx/streams/' + id); + }, + + /** + * @param {Number} id + * @returns {Promise} + */ + enable: function (id) { + return fetch('post', 'nginx/streams/' + id + '/enable'); + }, + + /** + * @param {Number} id + * @returns {Promise} + */ + disable: function (id) { + return fetch('post', 'nginx/streams/' + id + '/disable'); } }, @@ -428,7 +484,7 @@ module.exports = { /** * @param {Object} data - * @param {Integer} data.id + * @param {Number} data.id * @returns {Promise} */ update: function (data) { @@ -438,7 +494,7 @@ module.exports = { }, /** - * @param {Integer} id + * @param {Number} id * @returns {Promise} */ delete: function (id) { @@ -446,12 +502,28 @@ module.exports = { }, /** - * @param {Integer} id + * @param {Number} id * @param {FormData} form_data * @params {Promise} */ setCerts: function (id, form_data) { return FileUpload('nginx/dead-hosts/' + id + '/certificates', form_data); + }, + + /** + * @param {Number} id + * @returns {Promise} + */ + enable: function (id) { + return fetch('post', 'nginx/dead-hosts/' + id + '/enable'); + }, + + /** + * @param {Number} id + * @returns {Promise} + */ + disable: function (id) { + return fetch('post', 'nginx/dead-hosts/' + id + '/disable'); } }, @@ -474,7 +546,7 @@ module.exports = { /** * @param {Object} data - * @param {Integer} data.id + * @param {Number} data.id * @returns {Promise} */ update: function (data) { @@ -484,7 +556,7 @@ module.exports = { }, /** - * @param {Integer} id + * @param {Number} id * @returns {Promise} */ delete: function (id) { @@ -511,7 +583,7 @@ module.exports = { /** * @param {Object} data - * @param {Integer} data.id + * @param {Number} data.id * @returns {Promise} */ update: function (data) { @@ -521,7 +593,7 @@ module.exports = { }, /** - * @param {Integer} id + * @param {Number} id * @returns {Promise} */ delete: function (id) { @@ -529,7 +601,7 @@ module.exports = { }, /** - * @param {Integer} id + * @param {Number} id * @param {FormData} form_data * @params {Promise} */ diff --git a/src/frontend/js/app/nginx/proxy/list/item.ejs b/src/frontend/js/app/nginx/proxy/list/item.ejs index d91a598b..b1ce0449 100644 --- a/src/frontend/js/app/nginx/proxy/list/item.ejs +++ b/src/frontend/js/app/nginx/proxy/list/item.ejs @@ -34,7 +34,9 @@ <% var o = isOnline(); - if (o === true) { %> + if (!enabled) { %> + <%- i18n('str', 'disabled') %> + <% } else if (o === true) { %> <%- i18n('str', 'online') %> <% } else if (o === false) { %> <%- i18n('str', 'offline') %> @@ -48,7 +50,7 @@ diff --git a/src/frontend/js/app/nginx/proxy/list/item.js b/src/frontend/js/app/nginx/proxy/list/item.js index 51ab72f9..9df2fd81 100644 --- a/src/frontend/js/app/nginx/proxy/list/item.js +++ b/src/frontend/js/app/nginx/proxy/list/item.js @@ -9,12 +9,25 @@ module.exports = Mn.View.extend({ tagName: 'tr', ui: { + able: 'a.able', edit: 'a.edit', delete: 'a.delete', host_link: '.host-link' }, events: { + 'click @ui.able': function (e) { + e.preventDefault(); + let id = this.model.get('id'); + App.Api.Nginx.ProxyHosts[this.model.get('enabled') ? 'disable' : 'enable'](id) + .then(() => { + return App.Api.Nginx.ProxyHosts.get(id) + .then(row => { + this.model.set(row); + }); + }); + }, + 'click @ui.edit': function (e) { e.preventDefault(); App.Controller.showNginxProxyForm(this.model); diff --git a/src/frontend/js/i18n/messages.json b/src/frontend/js/i18n/messages.json index c091439d..e3658a80 100644 --- a/src/frontend/js/i18n/messages.json +++ b/src/frontend/js/i18n/messages.json @@ -14,6 +14,8 @@ "save": "Save", "cancel": "Cancel", "close": "Close", + "enable": "Enable", + "disable": "Disable", "sure": "Yes I'm Sure", "disabled": "Disabled", "choose-file": "Choose file", diff --git a/src/frontend/js/models/proxy-host.js b/src/frontend/js/models/proxy-host.js index b8dfdf35..72c0737c 100644 --- a/src/frontend/js/models/proxy-host.js +++ b/src/frontend/js/models/proxy-host.js @@ -22,6 +22,7 @@ const model = Backbone.Model.extend({ block_exploits: false, http2_support: false, advanced_config: '', + enabled: true, meta: {}, // The following are expansions: owner: null,