diff --git a/backend/migrations/20200522144240_openid_allowed_users.js b/backend/migrations/20200522144240_openid_allowed_users.js new file mode 100644 index 0000000..9b4e0aa --- /dev/null +++ b/backend/migrations/20200522144240_openid_allowed_users.js @@ -0,0 +1,40 @@ +const migrate_name = 'openid_allowed_users'; +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('openidc_restrict_users_enabled').notNull().unsigned().defaultTo(0); + proxy_host.json('openidc_allowed_users').notNull().defaultTo([]); + }) + .then(() => { + logger.info('[' + migrate_name + '] proxy_host Table altered'); + }); +}; + +/** + * Undo Migrate + * + * @param {Object} knex + * @param {Promise} Promise + * @returns {Promise} + */ +exports.down = function (knex/*, Promise*/) { + return knex.schema.table('proxy_host', function (proxy_host) { + proxy_host.dropColumn('openidc_restrict_users_enabled'); + proxy_host.dropColumn('openidc_allowed_users'); + }) + .then(() => { + logger.info('[' + migrate_name + '] proxy_host Table altered'); + }); +}; diff --git a/backend/models/proxy_host.js b/backend/models/proxy_host.js index a758308..e86b802 100644 --- a/backend/models/proxy_host.js +++ b/backend/models/proxy_host.js @@ -20,12 +20,18 @@ class ProxyHost extends Model { this.domain_names = []; } + // Default for openidc_allowed_users + if (typeof this.openidc_allowed_users === 'undefined') { + this.openidc_allowed_users = []; + } + // Default for meta if (typeof this.meta === 'undefined') { this.meta = {}; } this.domain_names.sort(); + this.openidc_allowed_users.sort(); } $beforeUpdate () { @@ -35,6 +41,11 @@ class ProxyHost extends Model { if (typeof this.domain_names !== 'undefined') { this.domain_names.sort(); } + + // Sort openidc_allowed_users + if (typeof this.openidc_allowed_users !== 'undefined') { + this.openidc_allowed_users.sort(); + } } static get name () { @@ -46,7 +57,7 @@ class ProxyHost extends Model { } static get jsonAttributes () { - return ['domain_names', 'meta', 'locations']; + return ['domain_names', 'meta', 'locations', 'openidc_allowed_users']; } static get relationMappings () { diff --git a/backend/schema/definitions.json b/backend/schema/definitions.json index 87db39a..eda3605 100644 --- a/backend/schema/definitions.json +++ b/backend/schema/definitions.json @@ -256,6 +256,22 @@ }, "openidc_client_secret": { "type": "string" + }, + "openidc_restrict_users_enabled": { + "description": "Only allow a specific set of OpenID Connect emails to access the resource", + "example": true, + "type": "boolean" + }, + "openidc_allowed_users": { + "type": "array", + "minItems": 0, + "items": { + "type": "string", + "description": "Email Address", + "example": "john@example.com", + "format": "email", + "minLength": 1 + } } } } diff --git a/backend/schema/endpoints/proxy-hosts.json b/backend/schema/endpoints/proxy-hosts.json index 849a0f7..1186225 100644 --- a/backend/schema/endpoints/proxy-hosts.json +++ b/backend/schema/endpoints/proxy-hosts.json @@ -82,6 +82,12 @@ "openidc_client_secret": { "$ref": "../definitions.json#/definitions/openidc_client_secret" }, + "openidc_restrict_users_enabled": { + "$ref": "../definitions.json#/definitions/openidc_restrict_users_enabled" + }, + "openidc_allowed_users": { + "$ref": "../definitions.json#/definitions/openidc_allowed_users" + }, "enabled": { "$ref": "../definitions.json#/definitions/enabled" }, @@ -197,6 +203,12 @@ "openidc_client_secret": { "$ref": "#/definitions/openidc_client_secret" }, + "openidc_restrict_users_enabled": { + "$ref": "#/definitions/openidc_restrict_users_enabled" + }, + "openidc_allowed_users": { + "$ref": "#/definitions/openidc_allowed_users" + }, "enabled": { "$ref": "#/definitions/enabled" }, @@ -305,6 +317,12 @@ "openidc_client_secret": { "$ref": "#/definitions/openidc_client_secret" }, + "openidc_restrict_users_enabled": { + "$ref": "#/definitions/openidc_restrict_users_enabled" + }, + "openidc_allowed_users": { + "$ref": "#/definitions/openidc_allowed_users" + }, "enabled": { "$ref": "#/definitions/enabled" }, @@ -396,6 +414,12 @@ "openidc_client_secret": { "$ref": "#/definitions/openidc_client_secret" }, + "openidc_restrict_users_enabled": { + "$ref": "#/definitions/openidc_restrict_users_enabled" + }, + "openidc_allowed_users": { + "$ref": "#/definitions/openidc_allowed_users" + }, "enabled": { "$ref": "#/definitions/enabled" }, diff --git a/frontend/js/app/nginx/proxy/form.ejs b/frontend/js/app/nginx/proxy/form.ejs index 36b62bb..41acf6c 100644 --- a/frontend/js/app/nginx/proxy/form.ejs +++ b/frontend/js/app/nginx/proxy/form.ejs @@ -280,7 +280,7 @@ @@ -317,6 +317,23 @@ +
+
+
+ +
+
+
+
+ + +
+
+
diff --git a/frontend/js/app/nginx/proxy/form.js b/frontend/js/app/nginx/proxy/form.js index b72457a..16278eb 100644 --- a/frontend/js/app/nginx/proxy/form.js +++ b/frontend/js/app/nginx/proxy/form.js @@ -21,31 +21,34 @@ module.exports = Mn.View.extend({ 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', - openidc_enabled: 'input[name="openidc_enabled"]', - openidc: '.openidc' + 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', + openidc_enabled: 'input[name="openidc_enabled"]', + openidc_restrict_users_enabled: 'input[name="openidc_restrict_users_enabled"]', + openidc_allowed_users: 'input[name="openidc_allowed_users"]', + openidc: '.openidc', + openidc_users: '.openidc_users', }, regions: { @@ -135,9 +138,18 @@ module.exports = Mn.View.extend({ let checked = this.ui.openidc_enabled.prop('checked'); if (checked) { - this.ui.openidc.show().find('input').prop('required', true); + this.ui.openidc.show().find('input').prop('disabled', false); } else { - this.ui.openidc.hide().find('input').prop('required', false); + this.ui.openidc.hide().find('input').prop('disabled', true); + } + }, + + 'change @ui.openidc_restrict_users_enabled': function () { + let checked = this.ui.openidc_restrict_users_enabled.prop('checked'); + if (checked) { + this.ui.openidc_users.show().find('input').prop('disabled', false); + } else { + this.ui.openidc_users.hide().find('input').prop('disabled', true); } }, @@ -180,6 +192,13 @@ module.exports = Mn.View.extend({ data.hsts_subdomains = !!data.hsts_subdomains; data.ssl_forced = !!data.ssl_forced; data.openidc_enabled = data.openidc_enabled === '1'; + data.openidc_restrict_users_enabled = data.openidc_restrict_users_enabled === '1'; + + if (data.openidc_restrict_users_enabled) { + if (typeof data.openidc_allowed_users === 'string' && data.openidc_allowed_users) { + data.openidc_allowed_users = data.openidc_allowed_users.split(','); + } + } if (typeof data.meta === 'undefined') data.meta = {}; data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1; @@ -365,8 +384,21 @@ module.exports = Mn.View.extend({ }); // OpenID Connect - this.ui.openidc.hide().find('input').prop('required', false); + this.ui.openidc_allowed_users.selectize({ + delimiter: ',', + persist: false, + maxOptions: 15, + create: function (input) { + return { + value: input, + text: input + }; + } + }); + this.ui.openidc.hide().find('input').prop('disabled', true); + this.ui.openidc_users.hide().find('input').prop('disabled', true); this.ui.openidc_enabled.trigger('change'); + this.ui.openidc_restrict_users_enabled.trigger('change'); }, initialize: function (options) { diff --git a/frontend/js/models/proxy-host.js b/frontend/js/models/proxy-host.js index ef1f1f4..85429d1 100644 --- a/frontend/js/models/proxy-host.js +++ b/frontend/js/models/proxy-host.js @@ -28,6 +28,8 @@ const model = Backbone.Model.extend({ openidc_auth_method: 'client_secret_post', openidc_client_id: '', openidc_client_secret: '', + openidc_restrict_users_enabled: false, + openidc_allowed_users: [], enabled: true, meta: {}, // The following are expansions: