Skip to content

Commit c629deb

Browse files
author
Jamie Curnow
committed
WIP
1 parent c5450ea commit c629deb

34 files changed

+708
-293
lines changed

docker-compose.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ services:
1212
volumes:
1313
- ./data/letsencrypt:/etc/letsencrypt
1414
- .:/srv/app
15+
- ~/.yarnrc:/root/.yarnrc
16+
- ~/.npmrc:/root/.npmrc
1517
depends_on:
1618
- db
1719
links:

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
"jquery": "^3.3.1",
1919
"jquery-mask-plugin": "^1.14.15",
2020
"jquery-serializejson": "^2.8.1",
21+
"messageformat": "^2.0.2",
22+
"messageformat-loader": "^0.7.0",
2123
"mini-css-extract-plugin": "^0.4.0",
2224
"moment": "^2.22.2",
2325
"node-sass": "^4.9.0",

src/backend/internal/dead-host.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const internalDeadHost = {
2626
.where('is_deleted', 0)
2727
.groupBy('id')
2828
.omit(['is_deleted'])
29-
.orderBy('domain_name', 'ASC');
29+
.orderBy('domain_names', 'ASC');
3030

3131
if (access_data.permission_visibility !== 'all') {
3232
query.andWhere('owner_user_id', access.token.get('attrs').id);
@@ -35,7 +35,7 @@ const internalDeadHost = {
3535
// Query is used for searching
3636
if (typeof search_query === 'string') {
3737
query.where(function () {
38-
this.where('domain_name', 'like', '%' + search_query + '%');
38+
this.where('domain_names', 'like', '%' + search_query + '%');
3939
});
4040
}
4141

src/backend/internal/host.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
'use strict';
2+
3+
const _ = require('lodash');
4+
const error = require('../lib/error');
5+
const proxyHostModel = require('../models/proxy_host');
6+
const redirectionHostModel = require('../models/redirection_host');
7+
const deadHostModel = require('../models/dead_host');
8+
9+
const internalHost = {
10+
11+
/**
12+
* Internal use only, checks to see if the ___domain is already taken by any other record
13+
*
14+
* @param {String} hostname
15+
* @param {String} [ignore_type] 'proxy', 'redirection', 'dead'
16+
* @param {Integer} [ignore_id] Must be supplied if type was also supplied
17+
* @returns {Promise}
18+
*/
19+
isHostnameTaken: function (hostname, ignore_type, ignore_id) {
20+
let promises = [
21+
proxyHostModel
22+
.query()
23+
.where('is_deleted', 0)
24+
.andWhere('domain_names', 'like', '%' + hostname + '%'),
25+
redirectionHostModel
26+
.query()
27+
.where('is_deleted', 0)
28+
.andWhere('domain_names', 'like', '%' + hostname + '%'),
29+
deadHostModel
30+
.query()
31+
.where('is_deleted', 0)
32+
.andWhere('domain_names', 'like', '%' + hostname + '%')
33+
];
34+
35+
return Promise.all(promises)
36+
.then(promises_results => {
37+
let is_taken = false;
38+
39+
if (promises_results[0]) {
40+
// Proxy Hosts
41+
if (internalHost._checkHostnameRecordsTaken(hostname, promises_results[0], ignore_type === 'proxy' && ignore_id ? ignore_id : 0)) {
42+
is_taken = true;
43+
}
44+
}
45+
46+
if (promises_results[1]) {
47+
// Redirection Hosts
48+
if (internalHost._checkHostnameRecordsTaken(hostname, promises_results[1], ignore_type === 'redirection' && ignore_id ? ignore_id : 0)) {
49+
is_taken = true;
50+
}
51+
}
52+
53+
if (promises_results[1]) {
54+
// Dead Hosts
55+
if (internalHost._checkHostnameRecordsTaken(hostname, promises_results[2], ignore_type === 'dead' && ignore_id ? ignore_id : 0)) {
56+
is_taken = true;
57+
}
58+
}
59+
60+
return {
61+
hostname: hostname,
62+
is_taken: is_taken
63+
};
64+
});
65+
},
66+
67+
/**
68+
* Private call only
69+
*
70+
* @param {String} hostname
71+
* @param {Array} existing_rows
72+
* @param {Integer} [ignore_id]
73+
* @returns {Boolean}
74+
*/
75+
_checkHostnameRecordsTaken: function (hostname, existing_rows, ignore_id) {
76+
let is_taken = false;
77+
78+
if (existing_rows && existing_rows.length) {
79+
existing_rows.map(function (existing_row) {
80+
existing_row.domain_names.map(function (existing_hostname) {
81+
// Does this ___domain match?
82+
if (existing_hostname.toLowerCase() === hostname.toLowerCase()) {
83+
if (!ignore_id || ignore_id !== existing_row.id) {
84+
is_taken = true;
85+
}
86+
}
87+
});
88+
});
89+
}
90+
91+
return is_taken;
92+
}
93+
94+
};
95+
96+
module.exports = internalHost;

src/backend/internal/proxy-host.js

Lines changed: 68 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const _ = require('lodash');
44
const error = require('../lib/error');
55
const proxyHostModel = require('../models/proxy_host');
6+
const internalHost = require('./host');
67

78
function omissions () {
89
return ['is_deleted'];
@@ -16,60 +17,39 @@ const internalProxyHost = {
1617
* @returns {Promise}
1718
*/
1819
create: (access, data) => {
19-
let auth = data.auth || null;
20-
delete data.auth;
21-
22-
data.avatar = data.avatar || '';
23-
data.roles = data.roles || [];
24-
25-
if (typeof data.is_disabled !== 'undefined') {
26-
data.is_disabled = data.is_disabled ? 1 : 0;
27-
}
28-
2920
return access.can('proxy_hosts:create', data)
21+
.then(access_data => {
22+
// Get a list of the ___domain names and check each of them against existing records
23+
let domain_name_check_promises = [];
24+
25+
data.domain_names.map(function (domain_name) {
26+
domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name));
27+
});
28+
29+
return Promise.all(domain_name_check_promises)
30+
.then(check_results => {
31+
check_results.map(function (result) {
32+
if (result.is_taken) {
33+
throw new error.ValidationError(result.hostname + ' is already in use');
34+
}
35+
});
36+
});
37+
})
3038
.then(() => {
31-
data.avatar = gravatar.url(data.email, {default: 'mm'});
39+
// At this point the domains should have been checked
40+
data.owner_user_id = access.token.get('attrs').id;
41+
42+
if (typeof data.meta === 'undefined') {
43+
data.meta = {};
44+
}
3245

33-
return userModel
46+
return proxyHostModel
3447
.query()
3548
.omit(omissions())
3649
.insertAndFetch(data);
3750
})
38-
.then(user => {
39-
if (auth) {
40-
return authModel
41-
.query()
42-
.insert({
43-
user_id: user.id,
44-
type: auth.type,
45-
secret: auth.secret,
46-
meta: {}
47-
})
48-
.then(() => {
49-
return user;
50-
});
51-
} else {
52-
return user;
53-
}
54-
})
55-
.then(user => {
56-
// Create permissions row as well
57-
let is_admin = data.roles.indexOf('admin') !== -1;
58-
59-
return userPermissionModel
60-
.query()
61-
.insert({
62-
user_id: user.id,
63-
visibility: is_admin ? 'all' : 'user',
64-
proxy_hosts: 'manage',
65-
redirection_hosts: 'manage',
66-
dead_hosts: 'manage',
67-
streams: 'manage',
68-
access_lists: 'manage'
69-
})
70-
.then(() => {
71-
return internalProxyHost.get(access, {id: user.id, expand: ['permissions']});
72-
});
51+
.then(row => {
52+
return _.omit(row, omissions());
7353
});
7454
},
7555

@@ -82,63 +62,49 @@ const internalProxyHost = {
8262
* @return {Promise}
8363
*/
8464
update: (access, data) => {
85-
if (typeof data.is_disabled !== 'undefined') {
86-
data.is_disabled = data.is_disabled ? 1 : 0;
87-
}
88-
8965
return access.can('proxy_hosts:update', data.id)
90-
.then(() => {
91-
92-
// Make sure that the user being updated doesn't change their email to another user that is already using it
93-
// 1. get user we want to update
94-
return internalProxyHost.get(access, {id: data.id})
95-
.then(user => {
96-
97-
// 2. if email is to be changed, find other users with that email
98-
if (typeof data.email !== 'undefined') {
99-
data.email = data.email.toLowerCase().trim();
100-
101-
if (user.email !== data.email) {
102-
return internalProxyHost.isEmailAvailable(data.email, data.id)
103-
.then(available => {
104-
if (!available) {
105-
throw new error.ValidationError('Email address already in use - ' + data.email);
106-
}
107-
108-
return user;
109-
});
110-
}
111-
}
66+
.then(access_data => {
67+
// Get a list of the ___domain names and check each of them against existing records
68+
let domain_name_check_promises = [];
11269

113-
// No change to email:
114-
return user;
70+
if (typeof data.domain_names !== 'undefined') {
71+
data.domain_names.map(function (domain_name) {
72+
domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, 'proxy', data.id));
11573
});
74+
75+
return Promise.all(domain_name_check_promises)
76+
.then(check_results => {
77+
check_results.map(function (result) {
78+
if (result.is_taken) {
79+
throw new error.ValidationError(result.hostname + ' is already in use');
80+
}
81+
});
82+
});
83+
}
11684
})
117-
.then(user => {
118-
if (user.id !== data.id) {
85+
.then(() => {
86+
return internalProxyHost.get(access, {id: data.id});
87+
})
88+
.then(row => {
89+
if (row.id !== data.id) {
11990
// Sanity check that something crazy hasn't happened
120-
throw new error.InternalValidationError('User could not be updated, IDs do not match: ' + user.id + ' !== ' + data.id);
91+
throw new error.InternalValidationError('Proxy Host could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
12192
}
12293

123-
data.avatar = gravatar.url(data.email || user.email, {default: 'mm'});
124-
125-
return userModel
94+
return proxyHostModel
12695
.query()
12796
.omit(omissions())
128-
.patchAndFetchById(user.id, data)
129-
.then(saved_user => {
130-
return _.omit(saved_user, omissions());
97+
.patchAndFetchById(row.id, data)
98+
.then(saved_row => {
99+
return _.omit(saved_row, omissions());
131100
});
132-
})
133-
.then(() => {
134-
return internalProxyHost.get(access, {id: data.id});
135101
});
136102
},
137103

138104
/**
139105
* @param {Access} access
140-
* @param {Object} [data]
141-
* @param {Integer} [data.id] Defaults to the token user
106+
* @param {Object} data
107+
* @param {Integer} data.id
142108
* @param {Array} [data.expand]
143109
* @param {Array} [data.omit]
144110
* @return {Promise}
@@ -153,14 +119,18 @@ const internalProxyHost = {
153119
}
154120

155121
return access.can('proxy_hosts:get', data.id)
156-
.then(() => {
157-
let query = userModel
122+
.then(access_data => {
123+
let query = proxyHostModel
158124
.query()
159125
.where('is_deleted', 0)
160126
.andWhere('id', data.id)
161127
.allowEager('[permissions]')
162128
.first();
163129

130+
if (access_data.permission_visibility !== 'all') {
131+
query.andWhere('owner_user_id', access.token.get('attrs').id);
132+
}
133+
164134
// Custom omissions
165135
if (typeof data.omit !== 'undefined' && data.omit !== null) {
166136
query.omit(data.omit);
@@ -193,19 +163,14 @@ const internalProxyHost = {
193163
.then(() => {
194164
return internalProxyHost.get(access, {id: data.id});
195165
})
196-
.then(user => {
197-
if (!user) {
166+
.then(row => {
167+
if (!row) {
198168
throw new error.ItemNotFoundError(data.id);
199169
}
200170

201-
// Make sure user can't delete themselves
202-
if (user.id === access.token.get('attrs').id) {
203-
throw new error.PermissionError('You cannot delete yourself.');
204-
}
205-
206-
return userModel
171+
return proxyHostModel
207172
.query()
208-
.where('id', user.id)
173+
.where('id', row.id)
209174
.patch({
210175
is_deleted: 1
211176
});
@@ -231,7 +196,8 @@ const internalProxyHost = {
231196
.where('is_deleted', 0)
232197
.groupBy('id')
233198
.omit(['is_deleted'])
234-
.orderBy('domain_name', 'ASC');
199+
.allowEager('[owner,access_list]')
200+
.orderBy('domain_names', 'ASC');
235201

236202
if (access_data.permission_visibility !== 'all') {
237203
query.andWhere('owner_user_id', access.token.get('attrs').id);
@@ -240,7 +206,7 @@ const internalProxyHost = {
240206
// Query is used for searching
241207
if (typeof search_query === 'string') {
242208
query.where(function () {
243-
this.where('domain_name', 'like', '%' + search_query + '%');
209+
this.where('domain_names', 'like', '%' + search_query + '%');
244210
});
245211
}
246212

0 commit comments

Comments
 (0)