From e67d9de6dcf514ad5bb859acb21d22d4d7905c3a Mon Sep 17 00:00:00 2001 From: Thyke Adams <45541936+ThykeAdams@users.noreply.github.com> Date: Sat, 8 May 2021 19:34:28 -0400 Subject: [PATCH] Allow * (Wildcard) Domains to be used --- frontend/js/app/nginx/proxy/form.js | 652 ++++++++++++++-------------- 1 file changed, 327 insertions(+), 325 deletions(-) diff --git a/frontend/js/app/nginx/proxy/form.js b/frontend/js/app/nginx/proxy/form.js index 8802b958..d46102d6 100644 --- a/frontend/js/app/nginx/proxy/form.js +++ b/frontend/js/app/nginx/proxy/form.js @@ -1,369 +1,371 @@ -const Mn = require('backbone.marionette'); -const App = require('../../main'); -const ProxyHostModel = require('../../../models/proxy-host'); -const ProxyLocationModel = require('../../../models/proxy-host-location'); -const template = require('./form.ejs'); -const certListItemTemplate = require('../certificates-list-item.ejs'); -const accessListItemTemplate = require('./access-list-item.ejs'); -const CustomLocation = require('./location'); -const Helpers = require('../../../lib/helpers'); -const i18n = require('../../i18n'); -const dns_providers = require('../../../../../global/certbot-dns-plugins'); +const Mn = require("backbone.marionette"); +const App = require("../../main"); +const ProxyHostModel = require("../../../models/proxy-host"); +const ProxyLocationModel = require("../../../models/proxy-host-location"); +const template = require("./form.ejs"); +const certListItemTemplate = require("../certificates-list-item.ejs"); +const accessListItemTemplate = require("./access-list-item.ejs"); +const CustomLocation = require("./location"); +const Helpers = require("../../../lib/helpers"); +const i18n = require("../../i18n"); +const dns_providers = require("../../../../../global/certbot-dns-plugins"); - -require('jquery-serializejson'); -require('selectize'); +require("jquery-serializejson"); +require("selectize"); module.exports = Mn.View.extend({ - template: template, - className: 'modal-dialog', + template: template, + className: "modal-dialog", - locationsCollection: new ProxyLocationModel.Collection(), + locationsCollection: new ProxyLocationModel.Collection(), - ui: { - form: 'form', - domain_names: 'input[name="domain_names"]', - forward_host: 'input[name="forward_host"]', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save', - add_location_btn: 'button.add_location', - locations_container: '.locations_container', - le_error_info: '#le-error-info', - certificate_select: 'select[name="certificate_id"]', - access_list_select: 'select[name="access_list_id"]', - ssl_forced: 'input[name="ssl_forced"]', - hsts_enabled: 'input[name="hsts_enabled"]', - hsts_subdomains: 'input[name="hsts_subdomains"]', - http2_support: 'input[name="http2_support"]', - dns_challenge_switch: 'input[name="meta[dns_challenge]"]', - dns_challenge_content: '.dns-challenge', - dns_provider: 'select[name="meta[dns_provider]"]', - credentials_file_content: '.credentials-file-content', - dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]', - propagation_seconds: 'input[name="meta[propagation_seconds]"]', - forward_scheme: 'select[name="forward_scheme"]', - letsencrypt: '.letsencrypt' - }, + ui: { + form: "form", + domain_names: 'input[name="domain_names"]', + forward_host: 'input[name="forward_host"]', + buttons: ".modal-footer button", + cancel: "button.cancel", + save: "button.save", + add_location_btn: "button.add_location", + locations_container: ".locations_container", + le_error_info: "#le-error-info", + certificate_select: 'select[name="certificate_id"]', + access_list_select: 'select[name="access_list_id"]', + ssl_forced: 'input[name="ssl_forced"]', + hsts_enabled: 'input[name="hsts_enabled"]', + hsts_subdomains: 'input[name="hsts_subdomains"]', + http2_support: 'input[name="http2_support"]', + dns_challenge_switch: 'input[name="meta[dns_challenge]"]', + dns_challenge_content: ".dns-challenge", + dns_provider: 'select[name="meta[dns_provider]"]', + credentials_file_content: ".credentials-file-content", + dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]', + propagation_seconds: 'input[name="meta[propagation_seconds]"]', + forward_scheme: 'select[name="forward_scheme"]', + letsencrypt: ".letsencrypt" + }, - regions: { - locations_regions: '@ui.locations_container' - }, + regions: { + locations_regions: "@ui.locations_container" + }, - events: { - 'change @ui.certificate_select': function () { - let id = this.ui.certificate_select.val(); - if (id === 'new') { - this.ui.letsencrypt.show().find('input').prop('disabled', false); - this.ui.dns_challenge_content.hide(); - } else { - this.ui.letsencrypt.hide().find('input').prop('disabled', true); - } + events: { + "change @ui.certificate_select": function () { + let id = this.ui.certificate_select.val(); + if (id === "new") { + this.ui.letsencrypt.show().find("input").prop("disabled", false); + this.ui.dns_challenge_content.hide(); + } else { + this.ui.letsencrypt.hide().find("input").prop("disabled", true); + } - let enabled = id === 'new' || parseInt(id, 10) > 0; + let enabled = id === "new" || parseInt(id, 10) > 0; - let inputs = this.ui.ssl_forced.add(this.ui.http2_support); - inputs - .prop('disabled', !enabled) - .parents('.form-group') - .css('opacity', enabled ? 1 : 0.5); + let inputs = this.ui.ssl_forced.add(this.ui.http2_support); + inputs + .prop("disabled", !enabled) + .parents(".form-group") + .css("opacity", enabled ? 1 : 0.5); - if (!enabled) { - inputs.prop('checked', false); - } + if (!enabled) { + inputs.prop("checked", false); + } - inputs.trigger('change'); - }, + inputs.trigger("change"); + }, - 'change @ui.ssl_forced': function () { - let checked = this.ui.ssl_forced.prop('checked'); - this.ui.hsts_enabled - .prop('disabled', !checked) - .parents('.form-group') - .css('opacity', checked ? 1 : 0.5); + "change @ui.ssl_forced": function () { + let checked = this.ui.ssl_forced.prop("checked"); + this.ui.hsts_enabled + .prop("disabled", !checked) + .parents(".form-group") + .css("opacity", checked ? 1 : 0.5); - if (!checked) { - this.ui.hsts_enabled.prop('checked', false); - } + if (!checked) { + this.ui.hsts_enabled.prop("checked", false); + } - this.ui.hsts_enabled.trigger('change'); - }, + this.ui.hsts_enabled.trigger("change"); + }, - 'change @ui.hsts_enabled': function () { - let checked = this.ui.hsts_enabled.prop('checked'); - this.ui.hsts_subdomains - .prop('disabled', !checked) - .parents('.form-group') - .css('opacity', checked ? 1 : 0.5); + "change @ui.hsts_enabled": function () { + let checked = this.ui.hsts_enabled.prop("checked"); + this.ui.hsts_subdomains + .prop("disabled", !checked) + .parents(".form-group") + .css("opacity", checked ? 1 : 0.5); - if (!checked) { - this.ui.hsts_subdomains.prop('checked', false); - } - }, + if (!checked) { + this.ui.hsts_subdomains.prop("checked", false); + } + }, - 'change @ui.dns_challenge_switch': function () { - const checked = this.ui.dns_challenge_switch.prop('checked'); - if (checked) { - this.ui.dns_provider.prop('required', 'required'); - const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; - if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){ - this.ui.dns_provider_credentials.prop('required', 'required'); - } - this.ui.dns_challenge_content.show(); - } else { - this.ui.dns_provider.prop('required', false); - this.ui.dns_provider_credentials.prop('required', false); - this.ui.dns_challenge_content.hide(); - } - }, + "change @ui.dns_challenge_switch": function () { + const checked = this.ui.dns_challenge_switch.prop("checked"); + if (checked) { + this.ui.dns_provider.prop("required", "required"); + const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; + if (selected_provider != "" && dns_providers[selected_provider].credentials !== false) { + this.ui.dns_provider_credentials.prop("required", "required"); + } + this.ui.dns_challenge_content.show(); + } else { + this.ui.dns_provider.prop("required", false); + this.ui.dns_provider_credentials.prop("required", false); + this.ui.dns_challenge_content.hide(); + } + }, - 'change @ui.dns_provider': function () { - const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; - if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) { - this.ui.dns_provider_credentials.prop('required', 'required'); - this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials; - this.ui.credentials_file_content.show(); - } else { - this.ui.dns_provider_credentials.prop('required', false); - this.ui.credentials_file_content.hide(); - } - }, + "change @ui.dns_provider": function () { + const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; + if (selected_provider != "" && dns_providers[selected_provider].credentials !== false) { + this.ui.dns_provider_credentials.prop("required", "required"); + this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials; + this.ui.credentials_file_content.show(); + } else { + this.ui.dns_provider_credentials.prop("required", false); + this.ui.credentials_file_content.hide(); + } + }, - 'click @ui.add_location_btn': function (e) { - e.preventDefault(); - - const model = new ProxyLocationModel.Model(); - this.locationsCollection.add(model); - }, + "click @ui.add_location_btn": function (e) { + e.preventDefault(); - 'click @ui.save': function (e) { - e.preventDefault(); - this.ui.le_error_info.hide(); + const model = new ProxyLocationModel.Model(); + this.locationsCollection.add(model); + }, - if (!this.ui.form[0].checkValidity()) { - $('').hide().appendTo(this.ui.form).click().remove(); - return; - } + "click @ui.save": function (e) { + e.preventDefault(); + this.ui.le_error_info.hide(); - let view = this; - let data = this.ui.form.serializeJSON(); + if (!this.ui.form[0].checkValidity()) { + $('').hide().appendTo(this.ui.form).click().remove(); + return; + } - // Add locations - data.locations = []; - this.locationsCollection.models.forEach((location) => { - data.locations.push(location.toJSON()); - }); + let view = this; + let data = this.ui.form.serializeJSON(); - // Serialize collects path from custom locations - // This field must be removed from root object - delete data.path; + // Add locations + data.locations = []; + this.locationsCollection.models.forEach((location) => { + data.locations.push(location.toJSON()); + }); - // Manipulate - data.forward_port = parseInt(data.forward_port, 10); - data.block_exploits = !!data.block_exploits; - data.caching_enabled = !!data.caching_enabled; - data.allow_websocket_upgrade = !!data.allow_websocket_upgrade; - data.http2_support = !!data.http2_support; - data.hsts_enabled = !!data.hsts_enabled; - data.hsts_subdomains = !!data.hsts_subdomains; - data.ssl_forced = !!data.ssl_forced; - - if (typeof data.meta === 'undefined') data.meta = {}; - data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1; - data.meta.dns_challenge = data.meta.dns_challenge == 1; - - if(!data.meta.dns_challenge){ - data.meta.dns_provider = undefined; - data.meta.dns_provider_credentials = undefined; - data.meta.propagation_seconds = undefined; - } else { - if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined; - } + // Serialize collects path from custom locations + // This field must be removed from root object + delete data.path; - if (typeof data.domain_names === 'string' && data.domain_names) { - data.domain_names = data.domain_names.split(','); - } + // Manipulate + data.forward_port = parseInt(data.forward_port, 10); + data.block_exploits = !!data.block_exploits; + data.caching_enabled = !!data.caching_enabled; + data.allow_websocket_upgrade = !!data.allow_websocket_upgrade; + data.http2_support = !!data.http2_support; + data.hsts_enabled = !!data.hsts_enabled; + data.hsts_subdomains = !!data.hsts_subdomains; + data.ssl_forced = !!data.ssl_forced; - // Check for any domain names containing wildcards, which are not allowed with letsencrypt - if (data.certificate_id === 'new') { - let domain_err = false; - if (!data.meta.dns_challenge) { - data.domain_names.map(function (name) { - if (name.match(/\*/im)) { - domain_err = true; - } - }); - } + if (typeof data.meta === "undefined") data.meta = {}; + data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1; + data.meta.dns_challenge = data.meta.dns_challenge == 1; - if (domain_err) { - alert(i18n('ssl', 'no-wildcard-without-dns')); - return; - } - } else { - data.certificate_id = parseInt(data.certificate_id, 10); - } + if (!data.meta.dns_challenge) { + data.meta.dns_provider = undefined; + data.meta.dns_provider_credentials = undefined; + data.meta.propagation_seconds = undefined; + } else { + if (data.meta.propagation_seconds === "") data.meta.propagation_seconds = undefined; + } - let method = App.Api.Nginx.ProxyHosts.create; - let is_new = true; + if (typeof data.domain_names === "string" && data.domain_names) { + data.domain_names = data.domain_names.split(","); + } - if (this.model.get('id')) { - // edit - is_new = false; - method = App.Api.Nginx.ProxyHosts.update; - data.id = this.model.get('id'); - } + // Check for any domain names containing wildcards, which are not allowed with letsencrypt + if (data.certificate_id === "new") { + let domain_err = false; + if (!data.meta.dns_challenge) { + data.domain_names.map(function (name) { + if (name.match(/\*/im)) { + domain_err = true; + } + }); + } - this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); - this.ui.save.addClass('btn-loading'); + if (domain_err) { + alert(i18n("ssl", "no-wildcard-without-dns")); + return; + } + } else { + data.certificate_id = parseInt(data.certificate_id, 10); + } - method(data) - .then(result => { - view.model.set(result); + let method = App.Api.Nginx.ProxyHosts.create; + let is_new = true; - App.UI.closeModal(function () { - if (is_new) { - App.Controller.showNginxProxy(); - } - }); - }) - .catch(err => { - let more_info = ''; - if(err.code === 500 && err.debug){ - try{ - more_info = JSON.parse(err.debug).debug.stack.join("\n"); - } catch(e) {} - } - this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `
${more_info}`:''}`; - this.ui.le_error_info.show(); - this.ui.le_error_info[0].scrollIntoView(); - this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); - this.ui.save.removeClass('btn-loading'); - }); - } - }, + if (this.model.get("id")) { + // edit + is_new = false; + method = App.Api.Nginx.ProxyHosts.update; + data.id = this.model.get("id"); + } - templateContext: { - getLetsencryptEmail: function () { - return App.Cache.User.get('email'); - }, - getUseDnsChallenge: function () { - return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false; - }, - getDnsProvider: function () { - return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null; - }, - getDnsProviderCredentials: function () { - return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : ''; - }, - getPropagationSeconds: function () { - return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : ''; - }, - dns_plugins: dns_providers, - }, + this.ui.buttons.prop("disabled", true).addClass("btn-disabled"); + this.ui.save.addClass("btn-loading"); - onRender: function () { - let view = this; + method(data) + .then((result) => { + view.model.set(result); - this.ui.ssl_forced.trigger('change'); - this.ui.hsts_enabled.trigger('change'); + App.UI.closeModal(function () { + if (is_new) { + App.Controller.showNginxProxy(); + } + }); + }) + .catch((err) => { + let more_info = ""; + if (err.code === 500 && err.debug) { + try { + more_info = JSON.parse(err.debug).debug.stack.join("\n"); + } catch (e) {} + } + this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== "" ? `
${more_info}` : ""}`; + this.ui.le_error_info.show(); + this.ui.le_error_info[0].scrollIntoView(); + this.ui.buttons.prop("disabled", false).removeClass("btn-disabled"); + this.ui.save.removeClass("btn-loading"); + }); + } + }, - // Domain names - this.ui.domain_names.selectize({ - delimiter: ',', - persist: false, - maxOptions: 15, - create: function (input) { - return { - value: input, - text: input - }; - }, - createFilter: /^(?:\.)?(?:[^.*]+\.?)+[^.]$/ - }); + templateContext: { + getLetsencryptEmail: function () { + return App.Cache.User.get("email"); + }, + getUseDnsChallenge: function () { + return typeof this.meta.dns_challenge !== "undefined" ? this.meta.dns_challenge : false; + }, + getDnsProvider: function () { + return typeof this.meta.dns_provider !== "undefined" && this.meta.dns_provider != "" ? this.meta.dns_provider : null; + }, + getDnsProviderCredentials: function () { + return typeof this.meta.dns_provider_credentials !== "undefined" ? this.meta.dns_provider_credentials : ""; + }, + getPropagationSeconds: function () { + return typeof this.meta.propagation_seconds !== "undefined" ? this.meta.propagation_seconds : ""; + }, + dns_plugins: dns_providers + }, - // Access Lists - this.ui.access_list_select.selectize({ - valueField: 'id', - labelField: 'name', - searchField: ['name'], - create: false, - preload: true, - allowEmptyOption: true, - render: { - option: function (item) { - item.i18n = App.i18n; - item.formatDbDate = Helpers.formatDbDate; - return accessListItemTemplate(item); - } - }, - load: function (query, callback) { - App.Api.Nginx.AccessLists.getAll(['items', 'clients']) - .then(rows => { - callback(rows); - }) - .catch(err => { - console.error(err); - callback(); - }); - }, - onLoad: function () { - view.ui.access_list_select[0].selectize.setValue(view.model.get('access_list_id')); - } - }); + onRender: function () { + let view = this; - // Certificates - this.ui.le_error_info.hide(); - this.ui.dns_challenge_content.hide(); - this.ui.credentials_file_content.hide(); - this.ui.letsencrypt.hide(); - this.ui.certificate_select.selectize({ - valueField: 'id', - labelField: 'nice_name', - searchField: ['nice_name', 'domain_names'], - create: false, - preload: true, - allowEmptyOption: true, - render: { - option: function (item) { - item.i18n = App.i18n; - item.formatDbDate = Helpers.formatDbDate; - return certListItemTemplate(item); - } - }, - load: function (query, callback) { - App.Api.Nginx.Certificates.getAll() - .then(rows => { - callback(rows); - }) - .catch(err => { - console.error(err); - callback(); - }); - }, - onLoad: function () { - view.ui.certificate_select[0].selectize.setValue(view.model.get('certificate_id')); - } - }); - }, + this.ui.ssl_forced.trigger("change"); + this.ui.hsts_enabled.trigger("change"); - initialize: function (options) { - if (typeof options.model === 'undefined' || !options.model) { - this.model = new ProxyHostModel.Model(); - } + // Domain names + this.ui.domain_names.selectize({ + delimiter: ",", + persist: false, + maxOptions: 15, + create: function (input) { + return { + value: input, + text: input + }; + }, + createFilter: /^(?:[^.]+\.?)+[^.]$/ + }); - this.locationsCollection = new ProxyLocationModel.Collection(); + // Access Lists + this.ui.access_list_select.selectize({ + valueField: "id", + labelField: "name", + searchField: ["name"], + create: false, + preload: true, + allowEmptyOption: true, + render: { + option: function (item) { + item.i18n = App.i18n; + item.formatDbDate = Helpers.formatDbDate; + return accessListItemTemplate(item); + } + }, + load: function (query, callback) { + App.Api.Nginx.AccessLists.getAll(["items", "clients"]) + .then((rows) => { + callback(rows); + }) + .catch((err) => { + console.error(err); + callback(); + }); + }, + onLoad: function () { + view.ui.access_list_select[0].selectize.setValue(view.model.get("access_list_id")); + } + }); - // Custom locations - this.showChildView('locations_regions', new CustomLocation.LocationCollectionView({ - collection: this.locationsCollection - })); + // Certificates + this.ui.le_error_info.hide(); + this.ui.dns_challenge_content.hide(); + this.ui.credentials_file_content.hide(); + this.ui.letsencrypt.hide(); + this.ui.certificate_select.selectize({ + valueField: "id", + labelField: "nice_name", + searchField: ["nice_name", "domain_names"], + create: false, + preload: true, + allowEmptyOption: true, + render: { + option: function (item) { + item.i18n = App.i18n; + item.formatDbDate = Helpers.formatDbDate; + return certListItemTemplate(item); + } + }, + load: function (query, callback) { + App.Api.Nginx.Certificates.getAll() + .then((rows) => { + callback(rows); + }) + .catch((err) => { + console.error(err); + callback(); + }); + }, + onLoad: function () { + view.ui.certificate_select[0].selectize.setValue(view.model.get("certificate_id")); + } + }); + }, - // Check wether there are any location defined - if (options.model && Array.isArray(options.model.attributes.locations)) { - options.model.attributes.locations.forEach((location) => { - let m = new ProxyLocationModel.Model(location); - this.locationsCollection.add(m); - }); - } - } + initialize: function (options) { + if (typeof options.model === "undefined" || !options.model) { + this.model = new ProxyHostModel.Model(); + } + + this.locationsCollection = new ProxyLocationModel.Collection(); + + // Custom locations + this.showChildView( + "locations_regions", + new CustomLocation.LocationCollectionView({ + collection: this.locationsCollection + }) + ); + + // Check wether there are any location defined + if (options.model && Array.isArray(options.model.attributes.locations)) { + options.model.attributes.locations.forEach((location) => { + let m = new ProxyLocationModel.Model(location); + this.locationsCollection.add(m); + }); + } + } });