diff --git a/docker-compose.yml b/docker-compose.yml
index dee8bb50..f108e60d 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -12,6 +12,8 @@ services:
volumes:
- ./data/letsencrypt:/etc/letsencrypt
- .:/srv/app
+ - ~/.yarnrc:/root/.yarnrc
+ - ~/.npmrc:/root/.npmrc
depends_on:
- db
links:
diff --git a/src/backend/internal/dead-host.js b/src/backend/internal/dead-host.js
index 8c4636cb..6efaf5d8 100644
--- a/src/backend/internal/dead-host.js
+++ b/src/backend/internal/dead-host.js
@@ -26,7 +26,7 @@ const internalDeadHost = {
.where('is_deleted', 0)
.groupBy('id')
.omit(['is_deleted'])
- .orderBy('domain_name', 'ASC');
+ .orderBy('domain_names', 'ASC');
if (access_data.permission_visibility !== 'all') {
query.andWhere('owner_user_id', access.token.get('attrs').id);
@@ -35,7 +35,7 @@ const internalDeadHost = {
// Query is used for searching
if (typeof search_query === 'string') {
query.where(function () {
- this.where('domain_name', 'like', '%' + search_query + '%');
+ this.where('domain_names', 'like', '%' + search_query + '%');
});
}
diff --git a/src/backend/internal/host.js b/src/backend/internal/host.js
new file mode 100644
index 00000000..791954b5
--- /dev/null
+++ b/src/backend/internal/host.js
@@ -0,0 +1,96 @@
+'use strict';
+
+const _ = require('lodash');
+const error = require('../lib/error');
+const proxyHostModel = require('../models/proxy_host');
+const redirectionHostModel = require('../models/redirection_host');
+const deadHostModel = require('../models/dead_host');
+
+const internalHost = {
+
+ /**
+ * Internal use only, checks to see if the domain is already taken by any other record
+ *
+ * @param {String} hostname
+ * @param {String} [ignore_type] 'proxy', 'redirection', 'dead'
+ * @param {Integer} [ignore_id] Must be supplied if type was also supplied
+ * @returns {Promise}
+ */
+ isHostnameTaken: function (hostname, ignore_type, ignore_id) {
+ let promises = [
+ proxyHostModel
+ .query()
+ .where('is_deleted', 0)
+ .andWhere('domain_names', 'like', '%' + hostname + '%'),
+ redirectionHostModel
+ .query()
+ .where('is_deleted', 0)
+ .andWhere('domain_names', 'like', '%' + hostname + '%'),
+ deadHostModel
+ .query()
+ .where('is_deleted', 0)
+ .andWhere('domain_names', 'like', '%' + hostname + '%')
+ ];
+
+ return Promise.all(promises)
+ .then(promises_results => {
+ let is_taken = false;
+
+ if (promises_results[0]) {
+ // Proxy Hosts
+ if (internalHost._checkHostnameRecordsTaken(hostname, promises_results[0], ignore_type === 'proxy' && ignore_id ? ignore_id : 0)) {
+ is_taken = true;
+ }
+ }
+
+ if (promises_results[1]) {
+ // Redirection Hosts
+ if (internalHost._checkHostnameRecordsTaken(hostname, promises_results[1], ignore_type === 'redirection' && ignore_id ? ignore_id : 0)) {
+ is_taken = true;
+ }
+ }
+
+ if (promises_results[1]) {
+ // Dead Hosts
+ if (internalHost._checkHostnameRecordsTaken(hostname, promises_results[2], ignore_type === 'dead' && ignore_id ? ignore_id : 0)) {
+ is_taken = true;
+ }
+ }
+
+ return {
+ hostname: hostname,
+ is_taken: is_taken
+ };
+ });
+ },
+
+ /**
+ * Private call only
+ *
+ * @param {String} hostname
+ * @param {Array} existing_rows
+ * @param {Integer} [ignore_id]
+ * @returns {Boolean}
+ */
+ _checkHostnameRecordsTaken: function (hostname, existing_rows, ignore_id) {
+ let is_taken = false;
+
+ if (existing_rows && existing_rows.length) {
+ existing_rows.map(function (existing_row) {
+ existing_row.domain_names.map(function (existing_hostname) {
+ // Does this domain match?
+ if (existing_hostname.toLowerCase() === hostname.toLowerCase()) {
+ if (!ignore_id || ignore_id !== existing_row.id) {
+ is_taken = true;
+ }
+ }
+ });
+ });
+ }
+
+ return is_taken;
+ }
+
+};
+
+module.exports = internalHost;
diff --git a/src/backend/internal/proxy-host.js b/src/backend/internal/proxy-host.js
index 56a978d0..39ba50b0 100644
--- a/src/backend/internal/proxy-host.js
+++ b/src/backend/internal/proxy-host.js
@@ -3,6 +3,7 @@
const _ = require('lodash');
const error = require('../lib/error');
const proxyHostModel = require('../models/proxy_host');
+const internalHost = require('./host');
function omissions () {
return ['is_deleted'];
@@ -16,60 +17,39 @@ const internalProxyHost = {
* @returns {Promise}
*/
create: (access, data) => {
- let auth = data.auth || null;
- delete data.auth;
-
- data.avatar = data.avatar || '';
- data.roles = data.roles || [];
-
- if (typeof data.is_disabled !== 'undefined') {
- data.is_disabled = data.is_disabled ? 1 : 0;
- }
-
return access.can('proxy_hosts:create', data)
- .then(() => {
- data.avatar = gravatar.url(data.email, {default: 'mm'});
+ .then(access_data => {
+ // Get a list of the domain names and check each of them against existing records
+ let domain_name_check_promises = [];
- return userModel
+ data.domain_names.map(function (domain_name) {
+ domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name));
+ });
+
+ return Promise.all(domain_name_check_promises)
+ .then(check_results => {
+ check_results.map(function (result) {
+ if (result.is_taken) {
+ throw new error.ValidationError(result.hostname + ' is already in use');
+ }
+ });
+ });
+ })
+ .then(() => {
+ // At this point the domains should have been checked
+ data.owner_user_id = access.token.get('attrs').id;
+
+ if (typeof data.meta === 'undefined') {
+ data.meta = {};
+ }
+
+ return proxyHostModel
.query()
.omit(omissions())
.insertAndFetch(data);
})
- .then(user => {
- if (auth) {
- return authModel
- .query()
- .insert({
- user_id: user.id,
- type: auth.type,
- secret: auth.secret,
- meta: {}
- })
- .then(() => {
- return user;
- });
- } else {
- return user;
- }
- })
- .then(user => {
- // Create permissions row as well
- let is_admin = data.roles.indexOf('admin') !== -1;
-
- return userPermissionModel
- .query()
- .insert({
- user_id: user.id,
- visibility: is_admin ? 'all' : 'user',
- proxy_hosts: 'manage',
- redirection_hosts: 'manage',
- dead_hosts: 'manage',
- streams: 'manage',
- access_lists: 'manage'
- })
- .then(() => {
- return internalProxyHost.get(access, {id: user.id, expand: ['permissions']});
- });
+ .then(row => {
+ return _.omit(row, omissions());
});
},
@@ -82,63 +62,49 @@ const internalProxyHost = {
* @return {Promise}
*/
update: (access, data) => {
- if (typeof data.is_disabled !== 'undefined') {
- data.is_disabled = data.is_disabled ? 1 : 0;
- }
-
return access.can('proxy_hosts:update', data.id)
- .then(() => {
+ .then(access_data => {
+ // Get a list of the domain names and check each of them against existing records
+ let domain_name_check_promises = [];
- // Make sure that the user being updated doesn't change their email to another user that is already using it
- // 1. get user we want to update
- return internalProxyHost.get(access, {id: data.id})
- .then(user => {
-
- // 2. if email is to be changed, find other users with that email
- if (typeof data.email !== 'undefined') {
- data.email = data.email.toLowerCase().trim();
-
- if (user.email !== data.email) {
- return internalProxyHost.isEmailAvailable(data.email, data.id)
- .then(available => {
- if (!available) {
- throw new error.ValidationError('Email address already in use - ' + data.email);
- }
-
- return user;
- });
- }
- }
-
- // No change to email:
- return user;
+ if (typeof data.domain_names !== 'undefined') {
+ data.domain_names.map(function (domain_name) {
+ domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, 'proxy', data.id));
});
- })
- .then(user => {
- if (user.id !== data.id) {
- // Sanity check that something crazy hasn't happened
- throw new error.InternalValidationError('User could not be updated, IDs do not match: ' + user.id + ' !== ' + data.id);
+
+ return Promise.all(domain_name_check_promises)
+ .then(check_results => {
+ check_results.map(function (result) {
+ if (result.is_taken) {
+ throw new error.ValidationError(result.hostname + ' is already in use');
+ }
+ });
+ });
}
-
- data.avatar = gravatar.url(data.email || user.email, {default: 'mm'});
-
- return userModel
- .query()
- .omit(omissions())
- .patchAndFetchById(user.id, data)
- .then(saved_user => {
- return _.omit(saved_user, omissions());
- });
})
.then(() => {
return internalProxyHost.get(access, {id: data.id});
+ })
+ .then(row => {
+ if (row.id !== data.id) {
+ // Sanity check that something crazy hasn't happened
+ throw new error.InternalValidationError('Proxy Host could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
+ }
+
+ return proxyHostModel
+ .query()
+ .omit(omissions())
+ .patchAndFetchById(row.id, data)
+ .then(saved_row => {
+ return _.omit(saved_row, omissions());
+ });
});
},
/**
* @param {Access} access
- * @param {Object} [data]
- * @param {Integer} [data.id] Defaults to the token user
+ * @param {Object} data
+ * @param {Integer} data.id
* @param {Array} [data.expand]
* @param {Array} [data.omit]
* @return {Promise}
@@ -153,14 +119,18 @@ const internalProxyHost = {
}
return access.can('proxy_hosts:get', data.id)
- .then(() => {
- let query = userModel
+ .then(access_data => {
+ let query = proxyHostModel
.query()
.where('is_deleted', 0)
.andWhere('id', data.id)
.allowEager('[permissions]')
.first();
+ if (access_data.permission_visibility !== 'all') {
+ query.andWhere('owner_user_id', access.token.get('attrs').id);
+ }
+
// Custom omissions
if (typeof data.omit !== 'undefined' && data.omit !== null) {
query.omit(data.omit);
@@ -193,19 +163,14 @@ const internalProxyHost = {
.then(() => {
return internalProxyHost.get(access, {id: data.id});
})
- .then(user => {
- if (!user) {
+ .then(row => {
+ if (!row) {
throw new error.ItemNotFoundError(data.id);
}
- // Make sure user can't delete themselves
- if (user.id === access.token.get('attrs').id) {
- throw new error.PermissionError('You cannot delete yourself.');
- }
-
- return userModel
+ return proxyHostModel
.query()
- .where('id', user.id)
+ .where('id', row.id)
.patch({
is_deleted: 1
});
@@ -231,7 +196,8 @@ const internalProxyHost = {
.where('is_deleted', 0)
.groupBy('id')
.omit(['is_deleted'])
- .orderBy('domain_name', 'ASC');
+ .allowEager('[owner,access_list]')
+ .orderBy('domain_names', 'ASC');
if (access_data.permission_visibility !== 'all') {
query.andWhere('owner_user_id', access.token.get('attrs').id);
@@ -240,7 +206,7 @@ const internalProxyHost = {
// Query is used for searching
if (typeof search_query === 'string') {
query.where(function () {
- this.where('domain_name', 'like', '%' + search_query + '%');
+ this.where('domain_names', 'like', '%' + search_query + '%');
});
}
diff --git a/src/backend/internal/redirection-host.js b/src/backend/internal/redirection-host.js
index bb37e977..0ec8ab84 100644
--- a/src/backend/internal/redirection-host.js
+++ b/src/backend/internal/redirection-host.js
@@ -26,7 +26,7 @@ const internalProxyHost = {
.where('is_deleted', 0)
.groupBy('id')
.omit(['is_deleted'])
- .orderBy('domain_name', 'ASC');
+ .orderBy('domain_names', 'ASC');
if (access_data.permission_visibility !== 'all') {
query.andWhere('owner_user_id', access.token.get('attrs').id);
@@ -35,7 +35,7 @@ const internalProxyHost = {
// Query is used for searching
if (typeof search_query === 'string') {
query.where(function () {
- this.where('domain_name', 'like', '%' + search_query + '%');
+ this.where('domain_names', 'like', '%' + search_query + '%');
});
}
diff --git a/src/backend/internal/user.js b/src/backend/internal/user.js
index 7a487b69..9cbf63d0 100644
--- a/src/backend/internal/user.js
+++ b/src/backend/internal/user.js
@@ -290,6 +290,7 @@ const internalUser = {
.where('is_deleted', 0)
.groupBy('id')
.omit(['is_deleted'])
+ .allowEager('[permissions]')
.orderBy('name', 'ASC');
// Query is used for searching
diff --git a/src/backend/lib/access.js b/src/backend/lib/access.js
index 04bf1964..4b403592 100644
--- a/src/backend/lib/access.js
+++ b/src/backend/lib/access.js
@@ -301,8 +301,8 @@ module.exports = function (token_string) {
});
})
.catch(err => {
- //logger.error(err.message);
- //logger.error(err.errors);
+ logger.error(err.message);
+ logger.error(err.errors);
throw new error.PermissionError('Permission Denied', err);
});
diff --git a/src/backend/lib/access/proxy_hosts-create.json b/src/backend/lib/access/proxy_hosts-create.json
new file mode 100644
index 00000000..3ceb86ca
--- /dev/null
+++ b/src/backend/lib/access/proxy_hosts-create.json
@@ -0,0 +1,23 @@
+{
+ "anyOf": [
+ {
+ "$ref": "roles#/definitions/admin"
+ },
+ {
+ "type": "object",
+ "required": ["permission_proxy_hosts", "roles"],
+ "properties": {
+ "permission_proxy_hosts": {
+ "$ref": "perms#/definitions/manage"
+ },
+ "roles": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": ["user"]
+ }
+ }
+ }
+ }
+ ]
+}
diff --git a/src/backend/lib/access/proxy_hosts-delete.json b/src/backend/lib/access/proxy_hosts-delete.json
new file mode 100644
index 00000000..3ceb86ca
--- /dev/null
+++ b/src/backend/lib/access/proxy_hosts-delete.json
@@ -0,0 +1,23 @@
+{
+ "anyOf": [
+ {
+ "$ref": "roles#/definitions/admin"
+ },
+ {
+ "type": "object",
+ "required": ["permission_proxy_hosts", "roles"],
+ "properties": {
+ "permission_proxy_hosts": {
+ "$ref": "perms#/definitions/manage"
+ },
+ "roles": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": ["user"]
+ }
+ }
+ }
+ }
+ ]
+}
diff --git a/src/backend/lib/access/proxy_hosts-get.json b/src/backend/lib/access/proxy_hosts-get.json
new file mode 100644
index 00000000..10c47465
--- /dev/null
+++ b/src/backend/lib/access/proxy_hosts-get.json
@@ -0,0 +1,23 @@
+{
+ "anyOf": [
+ {
+ "$ref": "roles#/definitions/admin"
+ },
+ {
+ "type": "object",
+ "required": ["permission_proxy_hosts", "roles"],
+ "properties": {
+ "permission_proxy_hosts": {
+ "$ref": "perms#/definitions/view"
+ },
+ "roles": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": ["user"]
+ }
+ }
+ }
+ }
+ ]
+}
diff --git a/src/backend/lib/access/proxy_hosts-update.json b/src/backend/lib/access/proxy_hosts-update.json
new file mode 100644
index 00000000..3ceb86ca
--- /dev/null
+++ b/src/backend/lib/access/proxy_hosts-update.json
@@ -0,0 +1,23 @@
+{
+ "anyOf": [
+ {
+ "$ref": "roles#/definitions/admin"
+ },
+ {
+ "type": "object",
+ "required": ["permission_proxy_hosts", "roles"],
+ "properties": {
+ "permission_proxy_hosts": {
+ "$ref": "perms#/definitions/manage"
+ },
+ "roles": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": ["user"]
+ }
+ }
+ }
+ }
+ ]
+}
diff --git a/src/backend/migrations/20180618015850_initial.js b/src/backend/migrations/20180618015850_initial.js
index db29e156..893e6480 100644
--- a/src/backend/migrations/20180618015850_initial.js
+++ b/src/backend/migrations/20180618015850_initial.js
@@ -67,7 +67,7 @@ exports.up = function (knex/*, Promise*/) {
table.dateTime('modified_on').notNull();
table.integer('owner_user_id').notNull().unsigned();
table.integer('is_deleted').notNull().unsigned().defaultTo(0);
- table.string('domain_name').notNull();
+ table.json('domain_names').notNull();
table.string('forward_ip').notNull();
table.integer('forward_port').notNull().unsigned();
table.integer('access_list_id').notNull().unsigned().defaultTo(0);
@@ -88,7 +88,7 @@ exports.up = function (knex/*, Promise*/) {
table.dateTime('modified_on').notNull();
table.integer('owner_user_id').notNull().unsigned();
table.integer('is_deleted').notNull().unsigned().defaultTo(0);
- table.string('domain_name').notNull();
+ table.json('domain_names').notNull();
table.string('forward_domain_name').notNull();
table.integer('preserve_path').notNull().unsigned().defaultTo(0);
table.integer('ssl_enabled').notNull().unsigned().defaultTo(0);
@@ -106,7 +106,7 @@ exports.up = function (knex/*, Promise*/) {
table.dateTime('modified_on').notNull();
table.integer('owner_user_id').notNull().unsigned();
table.integer('is_deleted').notNull().unsigned().defaultTo(0);
- table.string('domain_name').notNull();
+ table.json('domain_names').notNull();
table.integer('ssl_enabled').notNull().unsigned().defaultTo(0);
table.string('ssl_provider').notNull().defaultTo('');
table.json('meta').notNull();
diff --git a/src/backend/models/access_list.js b/src/backend/models/access_list.js
new file mode 100644
index 00000000..d2e98332
--- /dev/null
+++ b/src/backend/models/access_list.js
@@ -0,0 +1,52 @@
+// Objection Docs:
+// http://vincit.github.io/objection.js/
+
+'use strict';
+
+const db = require('../db');
+const Model = require('objection').Model;
+const User = require('./user');
+
+Model.knex(db);
+
+class AccessList extends Model {
+ $beforeInsert () {
+ this.created_on = Model.raw('NOW()');
+ this.modified_on = Model.raw('NOW()');
+ }
+
+ $beforeUpdate () {
+ this.modified_on = Model.raw('NOW()');
+ }
+
+ static get name () {
+ return 'AccessList';
+ }
+
+ static get tableName () {
+ return 'access_list';
+ }
+
+ static get jsonAttributes () {
+ return ['meta'];
+ }
+
+ static get relationMappings () {
+ return {
+ owner: {
+ relation: Model.HasOneRelation,
+ modelClass: User,
+ join: {
+ from: 'access_list.owner_user_id',
+ to: 'user.id'
+ },
+ modify: function (qb) {
+ qb.where('user.is_deleted', 0);
+ qb.omit(['id', 'created_on', 'modified_on', 'is_deleted', 'email', 'roles']);
+ }
+ }
+ };
+ }
+}
+
+module.exports = AccessList;
diff --git a/src/backend/models/access_list_auth.js b/src/backend/models/access_list_auth.js
new file mode 100644
index 00000000..be64325c
--- /dev/null
+++ b/src/backend/models/access_list_auth.js
@@ -0,0 +1,51 @@
+// Objection Docs:
+// http://vincit.github.io/objection.js/
+
+'use strict';
+
+const db = require('../db');
+const Model = require('objection').Model;
+
+Model.knex(db);
+
+class AccessListAuth extends Model {
+ $beforeInsert () {
+ this.created_on = Model.raw('NOW()');
+ this.modified_on = Model.raw('NOW()');
+ }
+
+ $beforeUpdate () {
+ this.modified_on = Model.raw('NOW()');
+ }
+
+ static get name () {
+ return 'AccessListAuth';
+ }
+
+ static get tableName () {
+ return 'access_list_auth';
+ }
+
+ static get jsonAttributes () {
+ return ['meta'];
+ }
+
+ static get relationMappings () {
+ return {
+ access_list: {
+ relation: Model.HasOneRelation,
+ modelClass: './access_list',
+ join: {
+ from: 'access_list_auth.access_list_id',
+ to: 'access_list.id'
+ },
+ modify: function (qb) {
+ qb.where('access_list.is_deleted', 0);
+ qb.omit(['created_on', 'modified_on', 'is_deleted', 'access_list_id']);
+ }
+ }
+ };
+ }
+}
+
+module.exports = AccessListAuth;
diff --git a/src/backend/models/dead_host.js b/src/backend/models/dead_host.js
index 26e92921..b98aff06 100644
--- a/src/backend/models/dead_host.js
+++ b/src/backend/models/dead_host.js
@@ -27,6 +27,10 @@ class DeadHost extends Model {
return 'dead_host';
}
+ static get jsonAttributes () {
+ return ['domain_names', 'meta'];
+ }
+
static get relationMappings () {
return {
owner: {
@@ -38,7 +42,7 @@ class DeadHost extends Model {
},
modify: function (qb) {
qb.where('user.is_deleted', 0);
- qb.omit(['created_on', 'modified_on', 'is_deleted', 'email', 'roles']);
+ qb.omit(['id', 'created_on', 'modified_on', 'is_deleted', 'email', 'roles']);
}
}
};
diff --git a/src/backend/models/proxy_host.js b/src/backend/models/proxy_host.js
index 488bc723..280328aa 100644
--- a/src/backend/models/proxy_host.js
+++ b/src/backend/models/proxy_host.js
@@ -3,9 +3,10 @@
'use strict';
-const db = require('../db');
-const Model = require('objection').Model;
-const User = require('./user');
+const db = require('../db');
+const Model = require('objection').Model;
+const User = require('./user');
+const AccessList = require('./access_list');
Model.knex(db);
@@ -13,10 +14,14 @@ class ProxyHost extends Model {
$beforeInsert () {
this.created_on = Model.raw('NOW()');
this.modified_on = Model.raw('NOW()');
+ this.domain_names.sort();
}
$beforeUpdate () {
this.modified_on = Model.raw('NOW()');
+ if (typeof this.domain_names !== 'undefined') {
+ this.domain_names.sort();
+ }
}
static get name () {
@@ -27,9 +32,13 @@ class ProxyHost extends Model {
return 'proxy_host';
}
+ static get jsonAttributes () {
+ return ['domain_names', 'meta'];
+ }
+
static get relationMappings () {
return {
- owner: {
+ owner: {
relation: Model.HasOneRelation,
modelClass: User,
join: {
@@ -38,7 +47,19 @@ class ProxyHost extends Model {
},
modify: function (qb) {
qb.where('user.is_deleted', 0);
- qb.omit(['created_on', 'modified_on', 'is_deleted', 'email', 'roles']);
+ qb.omit(['id', 'created_on', 'modified_on', 'is_deleted', 'email', 'roles']);
+ }
+ },
+ access_list: {
+ relation: Model.HasOneRelation,
+ modelClass: AccessList,
+ join: {
+ from: 'proxy_host.access_list_id',
+ to: 'access_list.id'
+ },
+ modify: function (qb) {
+ qb.where('access_list.is_deleted', 0);
+ qb.omit(['id', 'created_on', 'modified_on', 'is_deleted']);
}
}
};
diff --git a/src/backend/models/redirection_host.js b/src/backend/models/redirection_host.js
index 7c5ab9dc..92f7790d 100644
--- a/src/backend/models/redirection_host.js
+++ b/src/backend/models/redirection_host.js
@@ -27,6 +27,10 @@ class RedirectionHost extends Model {
return 'redirection_host';
}
+ static get jsonAttributes () {
+ return ['domain_names', 'meta'];
+ }
+
static get relationMappings () {
return {
owner: {
@@ -38,7 +42,7 @@ class RedirectionHost extends Model {
},
modify: function (qb) {
qb.where('user.is_deleted', 0);
- qb.omit(['created_on', 'modified_on', 'is_deleted', 'email', 'roles']);
+ qb.omit(['id', 'created_on', 'modified_on', 'is_deleted', 'email', 'roles']);
}
}
};
diff --git a/src/backend/models/stream.js b/src/backend/models/stream.js
index f002a6c7..7e9f76b9 100644
--- a/src/backend/models/stream.js
+++ b/src/backend/models/stream.js
@@ -27,6 +27,10 @@ class Stream extends Model {
return 'stream';
}
+ static get jsonAttributes () {
+ return ['meta'];
+ }
+
static get relationMappings () {
return {
owner: {
@@ -38,7 +42,7 @@ class Stream extends Model {
},
modify: function (qb) {
qb.where('user.is_deleted', 0);
- qb.omit(['created_on', 'modified_on', 'is_deleted', 'email', 'roles']);
+ qb.omit(['id', 'created_on', 'modified_on', 'is_deleted', 'email', 'roles']);
}
}
};
diff --git a/src/backend/routes/api/nginx/proxy_hosts.js b/src/backend/routes/api/nginx/proxy_hosts.js
index 04cc4651..ad69f003 100644
--- a/src/backend/routes/api/nginx/proxy_hosts.js
+++ b/src/backend/routes/api/nginx/proxy_hosts.js
@@ -104,7 +104,7 @@ router
})
.then(data => {
return internalProxyHost.get(res.locals.access, {
- id: data.host_id,
+ id: parseInt(data.host_id, 10),
expand: data.expand
});
})
@@ -123,7 +123,7 @@ router
.put((req, res, next) => {
apiValidator({$ref: 'endpoints/proxy-hosts#/links/2/schema'}, req.body)
.then(payload => {
- payload.id = req.params.host_id;
+ payload.id = parseInt(req.params.host_id, 10);
return internalProxyHost.update(res.locals.access, payload);
})
.then(result => {
@@ -139,7 +139,7 @@ router
* Update and existing proxy-host
*/
.delete((req, res, next) => {
- internalProxyHost.delete(res.locals.access, {id: req.params.host_id})
+ internalProxyHost.delete(res.locals.access, {id: parseInt(req.params.host_id, 10)})
.then(result => {
res.status(200)
.send(result);
diff --git a/src/backend/schema/definitions.json b/src/backend/schema/definitions.json
index 43ff5560..e064b4d6 100644
--- a/src/backend/schema/definitions.json
+++ b/src/backend/schema/definitions.json
@@ -134,6 +134,31 @@
"type": "string",
"minLength": 8,
"maxLength": 255
+ },
+ "domain_names": {
+ "description": "Domain Names separated by a comma",
+ "example": "*.jc21.com,blog.jc21.com",
+ "type": "array",
+ "maxItems": 15,
+ "uniqueItems": true,
+ "items": {
+ "type": "string",
+ "pattern": "^(?:\\*\\.)?(?:[^.*]+\\.?)+[^.]$"
+ }
+ },
+ "ssl_enabled": {
+ "description": "Is SSL Enabled",
+ "example": true,
+ "type": "boolean"
+ },
+ "ssl_forced": {
+ "description": "Is SSL Forced",
+ "example": false,
+ "type": "boolean"
+ },
+ "ssl_provider": {
+ "type": "string",
+ "pattern": "^(letsencrypt|other)$"
}
}
}
diff --git a/src/backend/schema/endpoints/proxy-hosts.json b/src/backend/schema/endpoints/proxy-hosts.json
index 02f44965..e73ec0ba 100644
--- a/src/backend/schema/endpoints/proxy-hosts.json
+++ b/src/backend/schema/endpoints/proxy-hosts.json
@@ -1,7 +1,7 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "endpoints/proxy-hosts",
- "title": "Users",
+ "title": "Proxy Hosts",
"description": "Endpoints relating to Proxy Hosts",
"stability": "stable",
"type": "object",
@@ -15,49 +15,78 @@
"modified_on": {
"$ref": "../definitions.json#/definitions/modified_on"
},
- "name": {
- "description": "Name",
- "example": "Jamie Curnow",
+ "domain_names": {
+ "$ref": "../definitions.json#/definitions/domain_names"
+ },
+ "forward_ip": {
"type": "string",
- "minLength": 2,
- "maxLength": 100
+ "format": "ipv4"
},
- "nickname": {
- "description": "Nickname",
- "example": "Jamie",
- "type": "string",
- "minLength": 2,
- "maxLength": 50
+ "forward_port": {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 65535
},
- "email": {
- "$ref": "../definitions.json#/definitions/email"
+ "ssl_enabled": {
+ "$ref": "../definitions.json#/definitions/ssl_enabled"
},
- "avatar": {
- "description": "Avatar",
- "example": "http://somewhere.jpg",
- "type": "string",
- "minLength": 2,
- "maxLength": 150,
- "readOnly": true
+ "ssl_forced": {
+ "$ref": "../definitions.json#/definitions/ssl_forced"
},
- "roles": {
- "description": "Roles",
- "example": [
- "admin"
- ],
- "type": "array"
+ "ssl_provider": {
+ "$ref": "../definitions.json#/definitions/ssl_provider"
},
- "is_disabled": {
- "description": "Is Disabled",
- "example": false,
- "type": "boolean"
+ "meta": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "letsencrypt_email": {
+ "type": "string",
+ "format": "email"
+ },
+ "letsencrypt_agree": {
+ "type": "boolean"
+ }
+ }
+ }
+ },
+ "properties": {
+ "id": {
+ "$ref": "#/definitions/id"
+ },
+ "created_on": {
+ "$ref": "#/definitions/created_on"
+ },
+ "modified_on": {
+ "$ref": "#/definitions/modified_on"
+ },
+ "domain_names": {
+ "$ref": "#/definitions/domain_names"
+ },
+ "forward_ip": {
+ "$ref": "#/definitions/forward_ip"
+ },
+ "forward_port": {
+ "$ref": "#/definitions/forward_port"
+ },
+ "ssl_enabled": {
+ "$ref": "#/definitions/ssl_enabled"
+ },
+ "ssl_forced": {
+ "$ref": "#/definitions/ssl_forced"
+ },
+ "ssl_provider": {
+ "$ref": "#/definitions/ssl_provider"
+ },
+ "meta": {
+ "$ref": "#/definitions/meta"
}
},
"links": [
{
"title": "List",
- "description": "Returns a list of Users",
- "href": "/users",
+ "description": "Returns a list of Proxy Hosts",
+ "href": "/nginx/proxy-hosts",
"access": "private",
"method": "GET",
"rel": "self",
@@ -73,8 +102,8 @@
},
{
"title": "Create",
- "description": "Creates a new User",
- "href": "/users",
+ "description": "Creates a new Proxy Host",
+ "href": "/nginx/proxy-hosts",
"access": "private",
"method": "POST",
"rel": "create",
@@ -84,33 +113,31 @@
"schema": {
"type": "object",
"required": [
- "name",
- "nickname",
- "email"
+ "domain_names",
+ "forward_ip",
+ "forward_port"
],
"properties": {
- "name": {
- "$ref": "#/definitions/name"
+ "domain_names": {
+ "$ref": "#/definitions/domain_names"
},
- "nickname": {
- "$ref": "#/definitions/nickname"
+ "forward_ip": {
+ "$ref": "#/definitions/forward_ip"
},
- "email": {
- "$ref": "#/definitions/email"
+ "forward_port": {
+ "$ref": "#/definitions/forward_port"
},
- "roles": {
- "$ref": "#/definitions/roles"
+ "ssl_enabled": {
+ "$ref": "#/definitions/ssl_enabled"
},
- "is_disabled": {
- "$ref": "#/definitions/is_disabled"
+ "ssl_forced": {
+ "$ref": "#/definitions/ssl_forced"
},
- "auth": {
- "type": "object",
- "description": "Auth Credentials",
- "example": {
- "type": "password",
- "secret": "bigredhorsebanana"
- }
+ "ssl_provider": {
+ "$ref": "#/definitions/ssl_provider"
+ },
+ "meta": {
+ "$ref": "#/definitions/meta"
}
}
},
@@ -122,8 +149,8 @@
},
{
"title": "Update",
- "description": "Updates a existing User",
- "href": "/users/{definitions.identity.example}",
+ "description": "Updates a existing Proxy Host",
+ "href": "/nginx/proxy-hosts/{definitions.identity.example}",
"access": "private",
"method": "PUT",
"rel": "update",
@@ -133,20 +160,26 @@
"schema": {
"type": "object",
"properties": {
- "name": {
- "$ref": "#/definitions/name"
+ "domain_names": {
+ "$ref": "#/definitions/domain_names"
},
- "nickname": {
- "$ref": "#/definitions/nickname"
+ "forward_ip": {
+ "$ref": "#/definitions/forward_ip"
},
- "email": {
- "$ref": "#/definitions/email"
+ "forward_port": {
+ "$ref": "#/definitions/forward_port"
},
- "roles": {
- "$ref": "#/definitions/roles"
+ "ssl_enabled": {
+ "$ref": "#/definitions/ssl_enabled"
},
- "is_disabled": {
- "$ref": "#/definitions/is_disabled"
+ "ssl_forced": {
+ "$ref": "#/definitions/ssl_forced"
+ },
+ "ssl_provider": {
+ "$ref": "#/definitions/ssl_provider"
+ },
+ "meta": {
+ "$ref": "#/definitions/meta"
}
}
},
@@ -158,8 +191,8 @@
},
{
"title": "Delete",
- "description": "Deletes a existing User",
- "href": "/users/{definitions.identity.example}",
+ "description": "Deletes a existing Proxy Host",
+ "href": "/nginx/proxy-hosts/{definitions.identity.example}",
"access": "private",
"method": "DELETE",
"rel": "delete",
@@ -170,34 +203,5 @@
"type": "boolean"
}
}
- ],
- "properties": {
- "id": {
- "$ref": "#/definitions/id"
- },
- "created_on": {
- "$ref": "#/definitions/created_on"
- },
- "modified_on": {
- "$ref": "#/definitions/modified_on"
- },
- "name": {
- "$ref": "#/definitions/name"
- },
- "nickname": {
- "$ref": "#/definitions/nickname"
- },
- "email": {
- "$ref": "#/definitions/email"
- },
- "avatar": {
- "$ref": "#/definitions/avatar"
- },
- "roles": {
- "$ref": "#/definitions/roles"
- },
- "is_disabled": {
- "$ref": "#/definitions/is_disabled"
- }
- }
+ ]
}
diff --git a/src/frontend/js/app/api.js b/src/frontend/js/app/api.js
index b24d6fa9..0cb7529a 100644
--- a/src/frontend/js/app/api.js
+++ b/src/frontend/js/app/api.js
@@ -264,6 +264,25 @@ module.exports = {
*/
create: function (data) {
return fetch('post', 'nginx/proxy-hosts', data);
+ },
+
+ /**
+ * @param {Object} data
+ * @param {Integer} data.id
+ * @returns {Promise}
+ */
+ update: function (data) {
+ let id = data.id;
+ delete data.id;
+ return fetch('put', 'nginx/proxy-hosts/' + id, data);
+ },
+
+ /**
+ * @param {Integer} id
+ * @returns {Promise}
+ */
+ delete: function (id) {
+ return fetch('delete', 'nginx/proxy-hosts/' + id);
}
},
diff --git a/src/frontend/js/app/audit-log/list/main.ejs b/src/frontend/js/app/audit-log/list/main.ejs
index 83afd4be..ce893413 100644
--- a/src/frontend/js/app/audit-log/list/main.ejs
+++ b/src/frontend/js/app/audit-log/list/main.ejs
@@ -1,10 +1,10 @@
-
-Name
-Email
-Roles
-
+
+ Name
+ Email
+ Roles
+