diff --git a/src/backend/internal/certificate.js b/src/backend/internal/certificate.js index 474afd1..f36f366 100644 --- a/src/backend/internal/certificate.js +++ b/src/backend/internal/certificate.js @@ -27,6 +27,10 @@ const internalCertificate = { .then(() => { data.owner_user_id = access.token.get('attrs').id; + if (data.provider === 'letsencrypt') { + data.nice_name = data.domain_names.sort().join(', '); + } + return certificateModel .query() .omit(omissions()) @@ -246,6 +250,22 @@ const internalCertificate = { }); }, + /** + * @param {Access} access + * @param {Object} data + * @param {Array} data.domain_names + * @param {String} data.meta.letsencrypt_email + * @param {Boolean} data.meta.letsencrypt_agree + * @returns {Promise} + */ + createQuickCertificate: (access, data) => { + return internalCertificate.create(access, { + provider: 'letsencrypt', + domain_names: data.domain_names, + meta: data.meta + }); + }, + /** * Validates that the certs provided are good. * No access required here, nothing is changed or stored. diff --git a/src/backend/internal/proxy-host.js b/src/backend/internal/proxy-host.js index 68ca822..cdbf4f3 100644 --- a/src/backend/internal/proxy-host.js +++ b/src/backend/internal/proxy-host.js @@ -1,11 +1,12 @@ 'use strict'; -const _ = require('lodash'); -const error = require('../lib/error'); -const proxyHostModel = require('../models/proxy_host'); -const internalHost = require('./host'); -const internalNginx = require('./nginx'); -const internalAuditLog = require('./audit-log'); +const _ = require('lodash'); +const error = require('../lib/error'); +const proxyHostModel = require('../models/proxy_host'); +const internalHost = require('./host'); +const internalNginx = require('./nginx'); +const internalAuditLog = require('./audit-log'); +const internalCertificate = require('./certificate'); function omissions () { return ['is_deleted']; @@ -19,6 +20,12 @@ const internalProxyHost = { * @returns {Promise} */ create: (access, data) => { + let create_certificate = data.certificate_id === 'new'; + + if (create_certificate) { + delete data.certificate_id; + } + return access.can('proxy_hosts:create', data) .then(access_data => { // Get a list of the domain names and check each of them against existing records @@ -46,14 +53,39 @@ const internalProxyHost = { .omit(omissions()) .insertAndFetch(data); }) + .then(row => { + if (create_certificate) { + return internalCertificate.createQuickCertificate(access, data) + .then(cert => { + // update host with cert id + return internalProxyHost.update(access, { + id: row.id, + certificate_id: cert.id + }); + }) + .then(() => { + return row; + }); + } else { + return row; + } + }) + .then(row => { + // re-fetch with cert + return internalProxyHost.get(access, { + id: row.id, + expand: ['certificate', 'owner'] + }); + }) .then(row => { // Configure nginx return internalNginx.configure(proxyHostModel, 'proxy_host', row) .then(() => { - return internalProxyHost.get(access, {id: row.id, expand: ['owner']}); + return row; }); }) .then(row => { + // Audit log data.meta = _.assign({}, data.meta || {}, row.meta); // Add to audit log @@ -78,6 +110,12 @@ const internalProxyHost = { * @return {Promise} */ update: (access, data) => { + let create_certificate = data.certificate_id === 'new'; + + if (create_certificate) { + delete data.certificate_id; + } + return access.can('proxy_hosts:update', data.id) .then(access_data => { // Get a list of the domain names and check each of them against existing records @@ -107,13 +145,28 @@ const internalProxyHost = { throw new error.InternalValidationError('Proxy Host 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 => { return proxyHostModel .query() - .omit(omissions()) - .patchAndFetchById(row.id, data) + .where({id: data.id}) + .patch(data) .then(saved_row => { - saved_row.meta = internalHost.cleanMeta(saved_row.meta); - // Add to audit log return internalAuditLog.add(access, { action: 'updated', @@ -125,6 +178,19 @@ const internalProxyHost = { return _.omit(saved_row, omissions()); }); }); + }) + .then(() => { + return internalProxyHost.get(access, { + id: data.id, + expand: ['owner', 'certificate'] + }) + .then(row => { + // Configure nginx + return internalNginx.configure(proxyHostModel, 'proxy_host', row) + .then(() => { + return _.omit(row, omissions()); + }); + }) }); }, @@ -167,7 +233,6 @@ const internalProxyHost = { }) .then(row => { if (row) { - row.meta = internalHost.cleanMeta(row.meta); return _.omit(row, omissions()); } else { throw new error.ItemNotFoundError(data.id); @@ -207,8 +272,6 @@ const internalProxyHost = { }) .then(() => { // Add to audit log - row.meta = internalHost.cleanMeta(row.meta); - return internalAuditLog.add(access, { action: 'deleted', object_type: 'proxy-host', @@ -257,13 +320,6 @@ const internalProxyHost = { } return query; - }) - .then(rows => { - rows.map(row => { - row.meta = internalHost.cleanMeta(row.meta); - }); - - return rows; }); }, diff --git a/src/backend/schema/definitions.json b/src/backend/schema/definitions.json index 75c148b..272b4c4 100644 --- a/src/backend/schema/definitions.json +++ b/src/backend/schema/definitions.json @@ -116,6 +116,20 @@ "type": "integer", "minimum": 1 }, + "certificate_id": { + "description": "Certificate ID", + "example": 1234, + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string", + "pattern": "^new$" + } + ] + }, "access_list_id": { "description": "Access List ID", "example": 1234, diff --git a/src/backend/schema/endpoints/proxy-hosts.json b/src/backend/schema/endpoints/proxy-hosts.json index 3f45960..d5352c8 100644 --- a/src/backend/schema/endpoints/proxy-hosts.json +++ b/src/backend/schema/endpoints/proxy-hosts.json @@ -27,15 +27,12 @@ "minimum": 1, "maximum": 65535 }, - "ssl_enabled": { - "$ref": "../definitions.json#/definitions/ssl_enabled" + "certificate_id": { + "$ref": "../definitions.json#/definitions/certificate_id" }, "ssl_forced": { "$ref": "../definitions.json#/definitions/ssl_forced" }, - "ssl_provider": { - "$ref": "../definitions.json#/definitions/ssl_provider" - }, "block_exploits": { "$ref": "../definitions.json#/definitions/block_exploits" }, @@ -46,17 +43,7 @@ "$ref": "../definitions.json#/definitions/access_list_id" }, "meta": { - "type": "object", - "additionalProperties": false, - "properties": { - "letsencrypt_email": { - "type": "string", - "format": "email" - }, - "letsencrypt_agree": { - "type": "boolean" - } - } + "type": "object" } }, "properties": { @@ -78,15 +65,12 @@ "forward_port": { "$ref": "#/definitions/forward_port" }, - "ssl_enabled": { - "$ref": "#/definitions/ssl_enabled" + "certificate_id": { + "$ref": "#/definitions/certificate_id" }, "ssl_forced": { "$ref": "#/definitions/ssl_forced" }, - "ssl_provider": { - "$ref": "#/definitions/ssl_provider" - }, "block_exploits": { "$ref": "#/definitions/block_exploits" }, @@ -146,15 +130,12 @@ "forward_port": { "$ref": "#/definitions/forward_port" }, - "ssl_enabled": { - "$ref": "#/definitions/ssl_enabled" + "certificate_id": { + "$ref": "#/definitions/certificate_id" }, "ssl_forced": { "$ref": "#/definitions/ssl_forced" }, - "ssl_provider": { - "$ref": "#/definitions/ssl_provider" - }, "block_exploits": { "$ref": "#/definitions/block_exploits" }, @@ -198,15 +179,12 @@ "forward_port": { "$ref": "#/definitions/forward_port" }, - "ssl_enabled": { - "$ref": "#/definitions/ssl_enabled" + "certificate_id": { + "$ref": "#/definitions/certificate_id" }, "ssl_forced": { "$ref": "#/definitions/ssl_forced" }, - "ssl_provider": { - "$ref": "#/definitions/ssl_provider" - }, "block_exploits": { "$ref": "#/definitions/block_exploits" }, diff --git a/src/frontend/js/app/nginx/proxy/delete.ejs b/src/frontend/js/app/nginx/proxy/delete.ejs index 61f1ca1..2fe099f 100644 --- a/src/frontend/js/app/nginx/proxy/delete.ejs +++ b/src/frontend/js/app/nginx/proxy/delete.ejs @@ -8,7 +8,7 @@
<%= i18n('proxy-hosts', 'delete-confirm', {domains: domain_names.join(', ')}) %> - <% if (ssl_enabled) { %> + <% if (certificate_id) { %>

<%- i18n('ssl', 'delete-ssl') %> <% } %> diff --git a/src/frontend/js/app/nginx/proxy/form.ejs b/src/frontend/js/app/nginx/proxy/form.ejs index 44aec7e..b6c6727 100644 --- a/src/frontend/js/app/nginx/proxy/form.ejs +++ b/src/frontend/js/app/nginx/proxy/form.ejs @@ -87,13 +87,13 @@
- +
diff --git a/src/frontend/js/app/nginx/proxy/form.js b/src/frontend/js/app/nginx/proxy/form.js index 52eb30b..b772011 100644 --- a/src/frontend/js/app/nginx/proxy/form.js +++ b/src/frontend/js/app/nginx/proxy/form.js @@ -24,7 +24,7 @@ module.exports = Mn.View.extend({ cancel: 'button.cancel', save: 'button.save', certificate_select: 'select[name="certificate_id"]', - ssl_options: '#ssl-options input', + ssl_forced: 'input[name="ssl_forced"]', letsencrypt: '.letsencrypt' }, @@ -38,7 +38,7 @@ module.exports = Mn.View.extend({ } let enabled = id === 'new' || parseInt(id, 10) > 0; - this.ui.ssl_options.prop('disabled', !enabled).parents('.form-group').css('opacity', enabled ? 1 : 0.5); + this.ui.ssl_forced.prop('disabled', !enabled).parents('.form-group').css('opacity', enabled ? 1 : 0.5); }, 'click @ui.save': function (e) { @@ -57,6 +57,10 @@ module.exports = Mn.View.extend({ data.block_exploits = !!data.block_exploits; data.caching_enabled = !!data.caching_enabled; + if (typeof data.ssl_forced !== 'undefined' && data.ssl_forced === '1') { + data.ssl_forced = true; + } + if (typeof data.domain_names === 'string' && data.domain_names) { data.domain_names = data.domain_names.split(','); } diff --git a/src/frontend/js/app/nginx/proxy/list/item.ejs b/src/frontend/js/app/nginx/proxy/list/item.ejs index 9d38e61..f68f93e 100644 --- a/src/frontend/js/app/nginx/proxy/list/item.ejs +++ b/src/frontend/js/app/nginx/proxy/list/item.ejs @@ -20,7 +20,7 @@
<%- forward_ip %>:<%- forward_port %>
-
<%- certificate ? i18n('ssl', certificate.provider) : i18n('ssl', 'none') %>
+
<%- certificate && certificate_id ? i18n('ssl', certificate.provider) : i18n('ssl', 'none') %>
<%- access_list_id ? access_list.name : i18n('str', 'public') %>