Convert backend to ESM

- About 5 years overdue
- Remove eslint, use bomejs instead
This commit is contained in:
Jamie Curnow
2025-09-02 21:43:00 +10:00
parent 5b6ca1bf00
commit a12553fec7
89 changed files with 4799 additions and 5107 deletions

View File

@@ -1,37 +1,37 @@
const _ = require('lodash');
const fs = require('node:fs');
const batchflow = require('batchflow');
const logger = require('../logger').access;
const error = require('../lib/error');
const utils = require('../lib/utils');
const accessListModel = require('../models/access_list');
const accessListAuthModel = require('../models/access_list_auth');
const accessListClientModel = require('../models/access_list_client');
const proxyHostModel = require('../models/proxy_host');
const internalAuditLog = require('./audit-log');
const internalNginx = require('./nginx');
import fs from "node:fs";
import batchflow from "batchflow";
import _ from "lodash";
import errs from "../lib/error.js";
import utils from "../lib/utils.js";
import { access as logger } from "../logger.js";
import accessListModel from "../models/access_list.js";
import accessListAuthModel from "../models/access_list_auth.js";
import accessListClientModel from "../models/access_list_client.js";
import proxyHostModel from "../models/proxy_host.js";
import internalAuditLog from "./audit-log.js";
import internalNginx from "./nginx.js";
function omissions () {
return ['is_deleted'];
}
const omissions = () => {
return ["is_deleted"];
};
const internalAccessList = {
/**
* @param {Access} access
* @param {Object} data
* @returns {Promise}
*/
create: (access, data) => {
return access.can('access_lists:create', data)
return access
.can("access_lists:create", data)
.then((/*access_data*/) => {
return accessListModel
.query()
.insertAndFetch({
name: data.name,
satisfy_any: data.satisfy_any,
pass_auth: data.pass_auth,
owner_user_id: access.token.getUserId(1)
name: data.name,
satisfy_any: data.satisfy_any,
pass_auth: data.pass_auth,
owner_user_id: access.token.getUserId(1),
})
.then(utils.omitRow(omissions()));
})
@@ -42,27 +42,27 @@ const internalAccessList = {
// Now add the items
data.items.map((item) => {
promises.push(accessListAuthModel
.query()
.insert({
promises.push(
accessListAuthModel.query().insert({
access_list_id: row.id,
username: item.username,
password: item.password
})
username: item.username,
password: item.password,
}),
);
return true;
});
// Now add the clients
if (typeof data.clients !== 'undefined' && data.clients) {
if (typeof data.clients !== "undefined" && data.clients) {
data.clients.map((client) => {
promises.push(accessListClientModel
.query()
.insert({
promises.push(
accessListClientModel.query().insert({
access_list_id: row.id,
address: client.address,
directive: client.directive
})
address: client.address,
directive: client.directive,
}),
);
return true;
});
}
@@ -70,28 +70,33 @@ const internalAccessList = {
})
.then(() => {
// re-fetch with expansions
return internalAccessList.get(access, {
id: data.id,
expand: ['owner', 'items', 'clients', 'proxy_hosts.access_list.[clients,items]']
}, true /* <- skip masking */);
return internalAccessList.get(
access,
{
id: data.id,
expand: ["owner", "items", "clients", "proxy_hosts.access_list.[clients,items]"],
},
true /* <- skip masking */,
);
})
.then((row) => {
// Audit log
data.meta = _.assign({}, data.meta || {}, row.meta);
return internalAccessList.build(row)
return internalAccessList
.build(row)
.then(() => {
if (parseInt(row.proxy_host_count, 10)) {
return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
if (Number.parseInt(row.proxy_host_count, 10)) {
return internalNginx.bulkGenerateConfigs("proxy_host", row.proxy_hosts);
}
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'created',
object_type: 'access-list',
object_id: row.id,
meta: internalAccessList.maskItems(data)
action: "created",
object_type: "access-list",
object_id: row.id,
meta: internalAccessList.maskItems(data),
});
})
.then(() => {
@@ -109,124 +114,122 @@ const internalAccessList = {
* @return {Promise}
*/
update: (access, data) => {
return access.can('access_lists:update', data.id)
return access
.can("access_lists:update", data.id)
.then((/*access_data*/) => {
return internalAccessList.get(access, {id: data.id});
return internalAccessList.get(access, { id: data.id });
})
.then((row) => {
if (row.id !== data.id) {
// Sanity check that something crazy hasn't happened
throw new error.InternalValidationError(`Access List could not be updated, IDs do not match: ${row.id} !== ${data.id}`);
throw new errs.InternalValidationError(
`Access List could not be updated, IDs do not match: ${row.id} !== ${data.id}`,
);
}
})
.then(() => {
// patch name if specified
if (typeof data.name !== 'undefined' && data.name) {
return accessListModel
.query()
.where({id: data.id})
.patch({
name: data.name,
satisfy_any: data.satisfy_any,
pass_auth: data.pass_auth,
});
if (typeof data.name !== "undefined" && data.name) {
return accessListModel.query().where({ id: data.id }).patch({
name: data.name,
satisfy_any: data.satisfy_any,
pass_auth: data.pass_auth,
});
}
})
.then(() => {
// Check for items and add/update/remove them
if (typeof data.items !== 'undefined' && data.items) {
const promises = [];
if (typeof data.items !== "undefined" && data.items) {
const promises = [];
const items_to_keep = [];
data.items.map((item) => {
if (item.password) {
promises.push(accessListAuthModel
.query()
.insert({
promises.push(
accessListAuthModel.query().insert({
access_list_id: data.id,
username: item.username,
password: item.password
})
username: item.username,
password: item.password,
}),
);
} else {
// This was supplied with an empty password, which means keep it but don't change the password
items_to_keep.push(item.username);
}
return true;
});
const query = accessListAuthModel
.query()
.delete()
.where('access_list_id', data.id);
const query = accessListAuthModel.query().delete().where("access_list_id", data.id);
if (items_to_keep.length) {
query.andWhere('username', 'NOT IN', items_to_keep);
query.andWhere("username", "NOT IN", items_to_keep);
}
return query
.then(() => {
// Add new items
if (promises.length) {
return Promise.all(promises);
}
});
return query.then(() => {
// Add new items
if (promises.length) {
return Promise.all(promises);
}
});
}
})
.then(() => {
// Check for clients and add/update/remove them
if (typeof data.clients !== 'undefined' && data.clients) {
if (typeof data.clients !== "undefined" && data.clients) {
const promises = [];
data.clients.map((client) => {
if (client.address) {
promises.push(accessListClientModel
.query()
.insert({
promises.push(
accessListClientModel.query().insert({
access_list_id: data.id,
address: client.address,
directive: client.directive
})
address: client.address,
directive: client.directive,
}),
);
}
return true;
});
const query = accessListClientModel
.query()
.delete()
.where('access_list_id', data.id);
const query = accessListClientModel.query().delete().where("access_list_id", data.id);
return query
.then(() => {
// Add new items
if (promises.length) {
return Promise.all(promises);
}
});
return query.then(() => {
// Add new items
if (promises.length) {
return Promise.all(promises);
}
});
}
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'updated',
object_type: 'access-list',
object_id: data.id,
meta: internalAccessList.maskItems(data)
action: "updated",
object_type: "access-list",
object_id: data.id,
meta: internalAccessList.maskItems(data),
});
})
.then(() => {
// re-fetch with expansions
return internalAccessList.get(access, {
id: data.id,
expand: ['owner', 'items', 'clients', 'proxy_hosts.[certificate,access_list.[clients,items]]']
}, true /* <- skip masking */);
return internalAccessList.get(
access,
{
id: data.id,
expand: ["owner", "items", "clients", "proxy_hosts.[certificate,access_list.[clients,items]]"],
},
true /* <- skip masking */,
);
})
.then((row) => {
return internalAccessList.build(row)
return internalAccessList
.build(row)
.then(() => {
if (parseInt(row.proxy_host_count, 10)) {
return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
if (Number.parseInt(row.proxy_host_count, 10)) {
return internalNginx.bulkGenerateConfigs("proxy_host", row.proxy_hosts);
}
}).then(internalNginx.reload)
})
.then(internalNginx.reload)
.then(() => {
return internalAccessList.maskItems(row);
});
@@ -243,47 +246,50 @@ const internalAccessList = {
* @return {Promise}
*/
get: (access, data, skip_masking) => {
if (typeof data === 'undefined') {
data = {};
}
const thisData = data || {};
return access.can('access_lists:get', data.id)
.then((access_data) => {
return access
.can("access_lists:get", thisData.id)
.then((accessData) => {
const query = accessListModel
.query()
.select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count'))
.leftJoin('proxy_host', function() {
this.on('proxy_host.access_list_id', '=', 'access_list.id')
.andOn('proxy_host.is_deleted', '=', 0);
.select("access_list.*", accessListModel.raw("COUNT(proxy_host.id) as proxy_host_count"))
.leftJoin("proxy_host", function () {
this.on("proxy_host.access_list_id", "=", "access_list.id").andOn(
"proxy_host.is_deleted",
"=",
0,
);
})
.where('access_list.is_deleted', 0)
.andWhere('access_list.id', data.id)
.groupBy('access_list.id')
.allowGraph('[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]')
.where("access_list.is_deleted", 0)
.andWhere("access_list.id", thisData.id)
.groupBy("access_list.id")
.allowGraph("[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]")
.first();
if (access_data.permission_visibility !== 'all') {
query.andWhere('access_list.owner_user_id', access.token.getUserId(1));
if (accessData.permission_visibility !== "all") {
query.andWhere("access_list.owner_user_id", access.token.getUserId(1));
}
if (typeof data.expand !== 'undefined' && data.expand !== null) {
query.withGraphFetched(`[${data.expand.join(', ')}]`);
if (typeof thisData.expand !== "undefined" && thisData.expand !== null) {
query.withGraphFetched(`[${thisData.expand.join(", ")}]`);
}
return query.then(utils.omitRow(omissions()));
})
.then((row) => {
let thisRow = row;
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
throw new errs.ItemNotFoundError(thisData.id);
}
if (!skip_masking && typeof row.items !== 'undefined' && row.items) {
row = internalAccessList.maskItems(row);
if (!skip_masking && typeof thisRow.items !== "undefined" && thisRow.items) {
thisRow = internalAccessList.maskItems(thisRow);
}
// Custom omissions
if (typeof data.omit !== 'undefined' && data.omit !== null) {
row = _.omit(row, data.omit);
if (typeof data.omit !== "undefined" && data.omit !== null) {
thisRow = _.omit(thisRow, data.omit);
}
return row;
return thisRow;
});
},
@@ -295,13 +301,14 @@ const internalAccessList = {
* @returns {Promise}
*/
delete: (access, data) => {
return access.can('access_lists:delete', data.id)
return access
.can("access_lists:delete", data.id)
.then(() => {
return internalAccessList.get(access, {id: data.id, expand: ['proxy_hosts', 'items', 'clients']});
return internalAccessList.get(access, { id: data.id, expand: ["proxy_hosts", "items", "clients"] });
})
.then((row) => {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
throw new errs.ItemNotFoundError(data.id);
}
// 1. update row to be deleted
@@ -312,26 +319,27 @@ const internalAccessList = {
// 1. update row to be deleted
return accessListModel
.query()
.where('id', row.id)
.where("id", row.id)
.patch({
is_deleted: 1
is_deleted: 1,
})
.then(() => {
// 2. update any proxy hosts that were using it (ignoring permissions)
if (row.proxy_hosts) {
return proxyHostModel
.query()
.where('access_list_id', '=', row.id)
.patch({access_list_id: 0})
.where("access_list_id", "=", row.id)
.patch({ access_list_id: 0 })
.then(() => {
// 3. reconfigure those hosts, then reload nginx
// set the access_list_id to zero for these items
row.proxy_hosts.map((_val, idx) => {
row.proxy_hosts[idx].access_list_id = 0;
return true;
});
return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
return internalNginx.bulkGenerateConfigs("proxy_host", row.proxy_hosts);
})
.then(() => {
return internalNginx.reload();
@@ -351,10 +359,10 @@ const internalAccessList = {
.then(() => {
// 4. audit log
return internalAuditLog.add(access, {
action: 'deleted',
object_type: 'access-list',
object_id: row.id,
meta: _.omit(internalAccessList.maskItems(row), ['is_deleted', 'proxy_hosts'])
action: "deleted",
object_type: "access-list",
object_id: row.id,
meta: _.omit(internalAccessList.maskItems(row), ["is_deleted", "proxy_hosts"]),
});
});
})
@@ -372,33 +380,37 @@ const internalAccessList = {
* @returns {Promise}
*/
getAll: (access, expand, search_query) => {
return access.can('access_lists:list')
return access
.can("access_lists:list")
.then((access_data) => {
const query = accessListModel
.query()
.select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count'))
.leftJoin('proxy_host', function() {
this.on('proxy_host.access_list_id', '=', 'access_list.id')
.andOn('proxy_host.is_deleted', '=', 0);
.select("access_list.*", accessListModel.raw("COUNT(proxy_host.id) as proxy_host_count"))
.leftJoin("proxy_host", function () {
this.on("proxy_host.access_list_id", "=", "access_list.id").andOn(
"proxy_host.is_deleted",
"=",
0,
);
})
.where('access_list.is_deleted', 0)
.groupBy('access_list.id')
.allowGraph('[owner,items,clients]')
.orderBy('access_list.name', 'ASC');
.where("access_list.is_deleted", 0)
.groupBy("access_list.id")
.allowGraph("[owner,items,clients]")
.orderBy("access_list.name", "ASC");
if (access_data.permission_visibility !== 'all') {
query.andWhere('access_list.owner_user_id', access.token.getUserId(1));
if (access_data.permission_visibility !== "all") {
query.andWhere("access_list.owner_user_id", access.token.getUserId(1));
}
// Query is used for searching
if (typeof search_query === 'string') {
if (typeof search_query === "string") {
query.where(function () {
this.where('name', 'like', `%${search_query}%`);
this.where("name", "like", `%${search_query}%`);
});
}
if (typeof expand !== 'undefined' && expand !== null) {
query.withGraphFetched(`[${expand.join(', ')}]`);
if (typeof expand !== "undefined" && expand !== null) {
query.withGraphFetched(`[${expand.join(", ")}]`);
}
return query.then(utils.omitRows(omissions()));
@@ -406,9 +418,10 @@ const internalAccessList = {
.then((rows) => {
if (rows) {
rows.map((row, idx) => {
if (typeof row.items !== 'undefined' && row.items) {
if (typeof row.items !== "undefined" && row.items) {
rows[idx] = internalAccessList.maskItems(row);
}
return true;
});
}
@@ -424,19 +437,15 @@ const internalAccessList = {
* @returns {Promise}
*/
getCount: (user_id, visibility) => {
const query = accessListModel
.query()
.count('id as count')
.where('is_deleted', 0);
const query = accessListModel.query().count("id as count").where("is_deleted", 0);
if (visibility !== 'all') {
query.andWhere('owner_user_id', user_id);
if (visibility !== "all") {
query.andWhere("owner_user_id", user_id);
}
return query.first()
.then((row) => {
return parseInt(row.count, 10);
});
return query.first().then((row) => {
return Number.parseInt(row.count, 10);
});
},
/**
@@ -444,18 +453,19 @@ const internalAccessList = {
* @returns {Object}
*/
maskItems: (list) => {
if (list && typeof list.items !== 'undefined') {
if (list && typeof list.items !== "undefined") {
list.items.map((val, idx) => {
let repeat_for = 8;
let first_char = '*';
let first_char = "*";
if (typeof val.password !== 'undefined' && val.password) {
if (typeof val.password !== "undefined" && val.password) {
repeat_for = val.password.length - 1;
first_char = val.password.charAt(0);
}
list.items[idx].hint = first_char + ('*').repeat(repeat_for);
list.items[idx].password = '';
list.items[idx].hint = first_char + "*".repeat(repeat_for);
list.items[idx].password = "";
return true;
});
}
@@ -493,48 +503,51 @@ const internalAccessList = {
// 2. create empty access file
try {
fs.writeFileSync(htpasswd_file, '', {encoding: 'utf8'});
fs.writeFileSync(htpasswd_file, "", { encoding: "utf8" });
resolve(htpasswd_file);
} catch (err) {
reject(err);
}
})
.then((htpasswd_file) => {
// 3. generate password for each user
if (list.items.length) {
return new Promise((resolve, reject) => {
batchflow(list.items).sequential()
.each((_i, item, next) => {
if (typeof item.password !== 'undefined' && item.password.length) {
logger.info(`Adding: ${item.username}`);
}).then((htpasswd_file) => {
// 3. generate password for each user
if (list.items.length) {
return new Promise((resolve, reject) => {
batchflow(list.items)
.sequential()
.each((_i, item, next) => {
if (typeof item.password !== "undefined" && item.password.length) {
logger.info(`Adding: ${item.username}`);
utils.execFile('openssl', ['passwd', '-apr1', item.password])
.then((res) => {
try {
fs.appendFileSync(htpasswd_file, `${item.username}:${res}\n`, {encoding: 'utf8'});
} catch (err) {
reject(err);
}
next();
})
.catch((err) => {
logger.error(err);
next(err);
});
}
})
.error((err) => {
logger.error(err);
reject(err);
})
.end((results) => {
logger.success(`Built Access file #${list.id} for: ${list.name}`);
resolve(results);
});
});
}
});
}
utils
.execFile("openssl", ["passwd", "-apr1", item.password])
.then((res) => {
try {
fs.appendFileSync(htpasswd_file, `${item.username}:${res}\n`, {
encoding: "utf8",
});
} catch (err) {
reject(err);
}
next();
})
.catch((err) => {
logger.error(err);
next(err);
});
}
})
.error((err) => {
logger.error(err);
reject(err);
})
.end((results) => {
logger.success(`Built Access file #${list.id} for: ${list.name}`);
resolve(results);
});
});
}
});
},
};
module.exports = internalAccessList;
export default internalAccessList;

View File

@@ -1,6 +1,6 @@
const error = require('../lib/error');
const auditLogModel = require('../models/audit-log');
const {castJsonIfNeed} = require('../lib/helpers');
import errs from "../lib/error.js";
import { castJsonIfNeed } from "../lib/helpers.js";
import auditLogModel from "../models/audit-log.js";
const internalAuditLog = {
@@ -13,28 +13,27 @@ const internalAuditLog = {
* @returns {Promise}
*/
getAll: (access, expand, search_query) => {
return access.can('auditlog:list')
.then(() => {
let query = auditLogModel
.query()
.orderBy('created_on', 'DESC')
.orderBy('id', 'DESC')
.limit(100)
.allowGraph('[user]');
return access.can("auditlog:list").then(() => {
const query = auditLogModel
.query()
.orderBy("created_on", "DESC")
.orderBy("id", "DESC")
.limit(100)
.allowGraph("[user]");
// Query is used for searching
if (typeof search_query === 'string' && search_query.length > 0) {
query.where(function () {
this.where(castJsonIfNeed('meta'), 'like', '%' + search_query + '%');
});
}
// Query is used for searching
if (typeof search_query === "string" && search_query.length > 0) {
query.where(function () {
this.where(castJsonIfNeed("meta"), "like", `%${search_query}`);
});
}
if (typeof expand !== 'undefined' && expand !== null) {
query.withGraphFetched('[' + expand.join(', ') + ']');
}
if (typeof expand !== "undefined" && expand !== null) {
query.withGraphFetched(`[${expand.join(", ")}]`);
}
return query;
});
return query;
});
},
/**
@@ -54,26 +53,26 @@ const internalAuditLog = {
add: (access, data) => {
return new Promise((resolve, reject) => {
// Default the user id
if (typeof data.user_id === 'undefined' || !data.user_id) {
if (typeof data.user_id === "undefined" || !data.user_id) {
data.user_id = access.token.getUserId(1);
}
if (typeof data.action === 'undefined' || !data.action) {
reject(new error.InternalValidationError('Audit log entry must contain an Action'));
if (typeof data.action === "undefined" || !data.action) {
reject(new errs.InternalValidationError("Audit log entry must contain an Action"));
} else {
// Make sure at least 1 of the IDs are set and action
resolve(auditLogModel
.query()
.insert({
user_id: data.user_id,
action: data.action,
object_type: data.object_type || '',
object_id: data.object_id || 0,
meta: data.meta || {}
}));
resolve(
auditLogModel.query().insert({
user_id: data.user_id,
action: data.action,
object_type: data.object_type || "",
object_id: data.object_id || 0,
meta: data.meta || {},
}),
);
}
});
}
},
};
module.exports = internalAuditLog;
export default internalAuditLog;

File diff suppressed because it is too large Load Diff

View File

@@ -1,106 +1,104 @@
const _ = require('lodash');
const error = require('../lib/error');
const utils = require('../lib/utils');
const deadHostModel = require('../models/dead_host');
const internalHost = require('./host');
const internalNginx = require('./nginx');
const internalAuditLog = require('./audit-log');
const internalCertificate = require('./certificate');
const {castJsonIfNeed} = require('../lib/helpers');
import _ from "lodash";
import errs from "../lib/error.js";
import { castJsonIfNeed } from "../lib/helpers.js";
import utils from "../lib/utils.js";
import deadHostModel from "../models/dead_host.js";
import internalAuditLog from "./audit-log.js";
import internalCertificate from "./certificate.js";
import internalHost from "./host.js";
import internalNginx from "./nginx.js";
function omissions () {
return ['is_deleted'];
}
const omissions = () => {
return ["is_deleted"];
};
const internalDeadHost = {
/**
* @param {Access} access
* @param {Object} data
* @returns {Promise}
*/
create: (access, data) => {
let create_certificate = data.certificate_id === 'new';
const createCertificate = data.certificate_id === "new";
if (create_certificate) {
if (createCertificate) {
delete data.certificate_id;
}
return access.can('dead_hosts:create', data)
return access
.can("dead_hosts:create", data)
.then((/*access_data*/) => {
// Get a list of the domain names and check each of them against existing records
let domain_name_check_promises = [];
const domain_name_check_promises = [];
data.domain_names.map(function (domain_name) {
data.domain_names.map((domain_name) => {
domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name));
return true;
});
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');
}
});
return Promise.all(domain_name_check_promises).then((check_results) => {
check_results.map((result) => {
if (result.is_taken) {
throw new errs.ValidationError(`${result.hostname} is already in use`);
}
return true;
});
});
})
.then(() => {
// At this point the domains should have been checked
data.owner_user_id = access.token.getUserId(1);
data = internalHost.cleanSslHstsData(data);
const thisData = internalHost.cleanSslHstsData(data);
// Fix for db field not having a default value
// for this optional field.
if (typeof data.advanced_config === 'undefined') {
data.advanced_config = '';
if (typeof data.advanced_config === "undefined") {
thisData.advanced_config = "";
}
return deadHostModel
.query()
.insertAndFetch(data)
.then(utils.omitRow(omissions()));
return deadHostModel.query().insertAndFetch(thisData).then(utils.omitRow(omissions()));
})
.then((row) => {
if (create_certificate) {
return internalCertificate.createQuickCertificate(access, data)
if (createCertificate) {
return internalCertificate
.createQuickCertificate(access, data)
.then((cert) => {
// update host with cert id
return internalDeadHost.update(access, {
id: row.id,
certificate_id: cert.id
id: row.id,
certificate_id: cert.id,
});
})
.then(() => {
return row;
});
} else {
return row;
}
return row;
})
.then((row) => {
// re-fetch with cert
return internalDeadHost.get(access, {
id: row.id,
expand: ['certificate', 'owner']
id: row.id,
expand: ["certificate", "owner"],
});
})
.then((row) => {
// Configure nginx
return internalNginx.configure(deadHostModel, 'dead_host', row)
.then(() => {
return row;
});
return internalNginx.configure(deadHostModel, "dead_host", row).then(() => {
return row;
});
})
.then((row) => {
data.meta = _.assign({}, data.meta || {}, row.meta);
// Add to audit log
return internalAuditLog.add(access, {
action: 'created',
object_type: 'dead-host',
object_id: row.id,
meta: data
})
return internalAuditLog
.add(access, {
action: "created",
object_type: "dead-host",
object_id: row.id,
meta: data,
})
.then(() => {
return row;
});
@@ -114,95 +112,104 @@ const internalDeadHost = {
* @return {Promise}
*/
update: (access, data) => {
let create_certificate = data.certificate_id === 'new';
let thisData = data;
const createCertificate = thisData.certificate_id === "new";
if (create_certificate) {
delete data.certificate_id;
if (createCertificate) {
delete thisData.certificate_id;
}
return access.can('dead_hosts:update', data.id)
return access
.can("dead_hosts:update", thisData.id)
.then((/*access_data*/) => {
// Get a list of the domain names and check each of them against existing records
let domain_name_check_promises = [];
const domain_name_check_promises = [];
if (typeof data.domain_names !== 'undefined') {
data.domain_names.map(function (domain_name) {
domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, 'dead', data.id));
if (typeof thisData.domain_names !== "undefined") {
thisData.domain_names.map((domain_name) => {
domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, "dead", data.id));
return true;
});
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');
}
});
return Promise.all(domain_name_check_promises).then((check_results) => {
check_results.map((result) => {
if (result.is_taken) {
throw new errs.ValidationError(`${result.hostname} is already in use`);
}
return true;
});
});
}
})
.then(() => {
return internalDeadHost.get(access, {id: data.id});
return internalDeadHost.get(access, { id: thisData.id });
})
.then((row) => {
if (row.id !== data.id) {
if (row.id !== thisData.id) {
// Sanity check that something crazy hasn't happened
throw new error.InternalValidationError('404 Host could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
throw new errs.InternalValidationError(
`404 Host could not be updated, IDs do not match: ${row.id} !== ${thisData.id}`,
);
}
if (create_certificate) {
return internalCertificate.createQuickCertificate(access, {
domain_names: data.domain_names || row.domain_names,
meta: _.assign({}, row.meta, data.meta)
})
if (createCertificate) {
return internalCertificate
.createQuickCertificate(access, {
domain_names: thisData.domain_names || row.domain_names,
meta: _.assign({}, row.meta, thisData.meta),
})
.then((cert) => {
// update host with cert id
data.certificate_id = cert.id;
thisData.certificate_id = cert.id;
})
.then(() => {
return row;
});
} else {
return row;
}
return row;
})
.then((row) => {
// Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here.
data = _.assign({}, {
domain_names: row.domain_names
}, data);
thisData = _.assign(
{},
{
domain_names: row.domain_names,
},
data,
);
data = internalHost.cleanSslHstsData(data, row);
thisData = internalHost.cleanSslHstsData(thisData, row);
return deadHostModel
.query()
.where({id: data.id})
.patch(data)
.where({ id: thisData.id })
.patch(thisData)
.then((saved_row) => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'updated',
object_type: 'dead-host',
object_id: row.id,
meta: data
})
return internalAuditLog
.add(access, {
action: "updated",
object_type: "dead-host",
object_id: row.id,
meta: thisData,
})
.then(() => {
return _.omit(saved_row, omissions());
});
});
})
.then(() => {
return internalDeadHost.get(access, {
id: data.id,
expand: ['owner', 'certificate']
})
return internalDeadHost
.get(access, {
id: thisData.id,
expand: ["owner", "certificate"],
})
.then((row) => {
// Configure nginx
return internalNginx.configure(deadHostModel, 'dead_host', row)
.then((new_meta) => {
row.meta = new_meta;
row = internalHost.cleanRowCertificateMeta(row);
return _.omit(row, omissions());
});
return internalNginx.configure(deadHostModel, "dead_host", row).then((new_meta) => {
row.meta = new_meta;
return _.omit(internalHost.cleanRowCertificateMeta(row), omissions());
});
});
});
},
@@ -216,36 +223,35 @@ const internalDeadHost = {
* @return {Promise}
*/
get: (access, data) => {
if (typeof data === 'undefined') {
data = {};
}
const thisData = data || {};
return access.can('dead_hosts:get', data.id)
return access
.can("dead_hosts:get", thisData.id)
.then((access_data) => {
let query = deadHostModel
const query = deadHostModel
.query()
.where('is_deleted', 0)
.andWhere('id', data.id)
.allowGraph('[owner,certificate]')
.where("is_deleted", 0)
.andWhere("id", dthisDataata.id)
.allowGraph("[owner,certificate]")
.first();
if (access_data.permission_visibility !== 'all') {
query.andWhere('owner_user_id', access.token.getUserId(1));
if (access_data.permission_visibility !== "all") {
query.andWhere("owner_user_id", access.token.getUserId(1));
}
if (typeof data.expand !== 'undefined' && data.expand !== null) {
query.withGraphFetched('[' + data.expand.join(', ') + ']');
if (typeof thisData.expand !== "undefined" && thisData.expand !== null) {
query.withGraphFetched(`[${data.expand.join(", ")}]`);
}
return query.then(utils.omitRow(omissions()));
})
.then((row) => {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
throw new errs.ItemNotFoundError(thisData.id);
}
// Custom omissions
if (typeof data.omit !== 'undefined' && data.omit !== null) {
row = _.omit(row, data.omit);
if (typeof thisData.omit !== "undefined" && thisData.omit !== null) {
return _.omit(row, thisData.omit);
}
return row;
});
@@ -259,35 +265,35 @@ const internalDeadHost = {
* @returns {Promise}
*/
delete: (access, data) => {
return access.can('dead_hosts:delete', data.id)
return access
.can("dead_hosts:delete", data.id)
.then(() => {
return internalDeadHost.get(access, {id: data.id});
return internalDeadHost.get(access, { id: data.id });
})
.then((row) => {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
throw new errs.ItemNotFoundError(data.id);
}
return deadHostModel
.query()
.where('id', row.id)
.where("id", row.id)
.patch({
is_deleted: 1
is_deleted: 1,
})
.then(() => {
// Delete Nginx Config
return internalNginx.deleteConfig('dead_host', row)
.then(() => {
return internalNginx.reload();
});
return internalNginx.deleteConfig("dead_host", row).then(() => {
return internalNginx.reload();
});
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'deleted',
object_type: 'dead-host',
object_id: row.id,
meta: _.omit(row, omissions())
action: "deleted",
object_type: "dead-host",
object_id: row.id,
meta: _.omit(row, omissions()),
});
});
})
@@ -304,39 +310,41 @@ const internalDeadHost = {
* @returns {Promise}
*/
enable: (access, data) => {
return access.can('dead_hosts:update', data.id)
return access
.can("dead_hosts:update", data.id)
.then(() => {
return internalDeadHost.get(access, {
id: data.id,
expand: ['certificate', 'owner']
id: data.id,
expand: ["certificate", "owner"],
});
})
.then((row) => {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
} else if (row.enabled) {
throw new error.ValidationError('Host is already enabled');
throw new errs.ItemNotFoundError(data.id);
}
if (row.enabled) {
throw new errs.ValidationError("Host is already enabled");
}
row.enabled = 1;
return deadHostModel
.query()
.where('id', row.id)
.where("id", row.id)
.patch({
enabled: 1
enabled: 1,
})
.then(() => {
// Configure nginx
return internalNginx.configure(deadHostModel, 'dead_host', row);
return internalNginx.configure(deadHostModel, "dead_host", row);
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'enabled',
object_type: 'dead-host',
object_id: row.id,
meta: _.omit(row, omissions())
action: "enabled",
object_type: "dead-host",
object_id: row.id,
meta: _.omit(row, omissions()),
});
});
})
@@ -353,39 +361,40 @@ const internalDeadHost = {
* @returns {Promise}
*/
disable: (access, data) => {
return access.can('dead_hosts:update', data.id)
return access
.can("dead_hosts:update", data.id)
.then(() => {
return internalDeadHost.get(access, {id: data.id});
return internalDeadHost.get(access, { id: data.id });
})
.then((row) => {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
} else if (!row.enabled) {
throw new error.ValidationError('Host is already disabled');
throw new errs.ItemNotFoundError(data.id);
}
if (!row.enabled) {
throw new errs.ValidationError("Host is already disabled");
}
row.enabled = 0;
return deadHostModel
.query()
.where('id', row.id)
.where("id", row.id)
.patch({
enabled: 0
enabled: 0,
})
.then(() => {
// Delete Nginx Config
return internalNginx.deleteConfig('dead_host', row)
.then(() => {
return internalNginx.reload();
});
return internalNginx.deleteConfig("dead_host", row).then(() => {
return internalNginx.reload();
});
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'disabled',
object_type: 'dead-host',
object_id: row.id,
meta: _.omit(row, omissions())
action: "disabled",
object_type: "dead-host",
object_id: row.id,
meta: _.omit(row, omissions()),
});
});
})
@@ -403,34 +412,35 @@ const internalDeadHost = {
* @returns {Promise}
*/
getAll: (access, expand, search_query) => {
return access.can('dead_hosts:list')
return access
.can("dead_hosts:list")
.then((access_data) => {
let query = deadHostModel
const query = deadHostModel
.query()
.where('is_deleted', 0)
.groupBy('id')
.allowGraph('[owner,certificate]')
.orderBy(castJsonIfNeed('domain_names'), 'ASC');
.where("is_deleted", 0)
.groupBy("id")
.allowGraph("[owner,certificate]")
.orderBy(castJsonIfNeed("domain_names"), "ASC");
if (access_data.permission_visibility !== 'all') {
query.andWhere('owner_user_id', access.token.getUserId(1));
if (access_data.permission_visibility !== "all") {
query.andWhere("owner_user_id", access.token.getUserId(1));
}
// Query is used for searching
if (typeof search_query === 'string' && search_query.length > 0) {
if (typeof search_query === "string" && search_query.length > 0) {
query.where(function () {
this.where(castJsonIfNeed('domain_names'), 'like', '%' + search_query + '%');
this.where(castJsonIfNeed("domain_names"), "like", `%${search_query}%`);
});
}
if (typeof expand !== 'undefined' && expand !== null) {
query.withGraphFetched('[' + expand.join(', ') + ']');
if (typeof expand !== "undefined" && expand !== null) {
query.withGraphFetched(`[${expand.join(", ")}]`);
}
return query.then(utils.omitRows(omissions()));
})
.then((rows) => {
if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) {
if (typeof expand !== "undefined" && expand !== null && expand.indexOf("certificate") !== -1) {
return internalHost.cleanAllRowsCertificateMeta(rows);
}
@@ -446,20 +456,16 @@ const internalDeadHost = {
* @returns {Promise}
*/
getCount: (user_id, visibility) => {
let query = deadHostModel
.query()
.count('id as count')
.where('is_deleted', 0);
const query = deadHostModel.query().count("id as count").where("is_deleted", 0);
if (visibility !== 'all') {
query.andWhere('owner_user_id', user_id);
if (visibility !== "all") {
query.andWhere("owner_user_id", user_id);
}
return query.first()
.then((row) => {
return parseInt(row.count, 10);
});
}
return query.first().then((row) => {
return Number.parseInt(row.count, 10);
});
},
};
module.exports = internalDeadHost;
export default internalDeadHost;

View File

@@ -1,11 +1,10 @@
const _ = require('lodash');
const proxyHostModel = require('../models/proxy_host');
const redirectionHostModel = require('../models/redirection_host');
const deadHostModel = require('../models/dead_host');
const {castJsonIfNeed} = require('../lib/helpers');
import _ from "lodash";
import { castJsonIfNeed } from "../lib/helpers.js";
import deadHostModel from "../models/dead_host.js";
import proxyHostModel from "../models/proxy_host.js";
import redirectionHostModel from "../models/redirection_host.js";
const internalHost = {
/**
* Makes sure that the ssl_* and hsts_* fields play nicely together.
* ie: if there is no cert, then force_ssl is off.
@@ -15,25 +14,23 @@ const internalHost = {
* @param {object} [existing_data]
* @returns {object}
*/
cleanSslHstsData: function (data, existing_data) {
existing_data = existing_data === undefined ? {} : existing_data;
cleanSslHstsData: (data, existingData) => {
const combinedData = _.assign({}, existingData || {}, data);
const combined_data = _.assign({}, existing_data, data);
if (!combined_data.certificate_id) {
combined_data.ssl_forced = false;
combined_data.http2_support = false;
if (!combinedData.certificate_id) {
combinedData.ssl_forced = false;
combinedData.http2_support = false;
}
if (!combined_data.ssl_forced) {
combined_data.hsts_enabled = false;
if (!combinedData.ssl_forced) {
combinedData.hsts_enabled = false;
}
if (!combined_data.hsts_enabled) {
combined_data.hsts_subdomains = false;
if (!combinedData.hsts_enabled) {
combinedData.hsts_subdomains = false;
}
return combined_data;
return combinedData;
},
/**
@@ -42,11 +39,12 @@ const internalHost = {
* @param {Array} rows
* @returns {Array}
*/
cleanAllRowsCertificateMeta: function (rows) {
rows.map(function (row, idx) {
if (typeof rows[idx].certificate !== 'undefined' && rows[idx].certificate) {
cleanAllRowsCertificateMeta: (rows) => {
rows.map((_, idx) => {
if (typeof rows[idx].certificate !== "undefined" && rows[idx].certificate) {
rows[idx].certificate.meta = {};
}
return true;
});
return rows;
@@ -58,8 +56,8 @@ const internalHost = {
* @param {Object} row
* @returns {Object}
*/
cleanRowCertificateMeta: function (row) {
if (typeof row.certificate !== 'undefined' && row.certificate) {
cleanRowCertificateMeta: (row) => {
if (typeof row.certificate !== "undefined" && row.certificate) {
row.certificate.meta = {};
}
@@ -73,48 +71,44 @@ const internalHost = {
* @param {Array} domain_names
* @returns {Promise}
*/
getHostsWithDomains: function (domain_names) {
getHostsWithDomains: (domain_names) => {
const promises = [
proxyHostModel
.query()
.where('is_deleted', 0),
redirectionHostModel
.query()
.where('is_deleted', 0),
deadHostModel
.query()
.where('is_deleted', 0)
proxyHostModel.query().where("is_deleted", 0),
redirectionHostModel.query().where("is_deleted", 0),
deadHostModel.query().where("is_deleted", 0),
];
return Promise.all(promises)
.then((promises_results) => {
let response_object = {
total_count: 0,
dead_hosts: [],
proxy_hosts: [],
redirection_hosts: []
};
return Promise.all(promises).then((promises_results) => {
const response_object = {
total_count: 0,
dead_hosts: [],
proxy_hosts: [],
redirection_hosts: [],
};
if (promises_results[0]) {
// Proxy Hosts
response_object.proxy_hosts = internalHost._getHostsWithDomains(promises_results[0], domain_names);
response_object.total_count += response_object.proxy_hosts.length;
}
if (promises_results[0]) {
// Proxy Hosts
response_object.proxy_hosts = internalHost._getHostsWithDomains(promises_results[0], domain_names);
response_object.total_count += response_object.proxy_hosts.length;
}
if (promises_results[1]) {
// Redirection Hosts
response_object.redirection_hosts = internalHost._getHostsWithDomains(promises_results[1], domain_names);
response_object.total_count += response_object.redirection_hosts.length;
}
if (promises_results[1]) {
// Redirection Hosts
response_object.redirection_hosts = internalHost._getHostsWithDomains(
promises_results[1],
domain_names,
);
response_object.total_count += response_object.redirection_hosts.length;
}
if (promises_results[2]) {
// Dead Hosts
response_object.dead_hosts = internalHost._getHostsWithDomains(promises_results[2], domain_names);
response_object.total_count += response_object.dead_hosts.length;
}
if (promises_results[2]) {
// Dead Hosts
response_object.dead_hosts = internalHost._getHostsWithDomains(promises_results[2], domain_names);
response_object.total_count += response_object.dead_hosts.length;
}
return response_object;
});
return response_object;
});
},
/**
@@ -125,112 +119,133 @@ const internalHost = {
* @param {Integer} [ignore_id] Must be supplied if type was also supplied
* @returns {Promise}
*/
isHostnameTaken: function (hostname, ignore_type, ignore_id) {
isHostnameTaken: (hostname, ignore_type, ignore_id) => {
const promises = [
proxyHostModel
.query()
.where('is_deleted', 0)
.andWhere(castJsonIfNeed('domain_names'), 'like', '%' + hostname + '%'),
.where("is_deleted", 0)
.andWhere(castJsonIfNeed("domain_names"), "like", `%${hostname}%`),
redirectionHostModel
.query()
.where('is_deleted', 0)
.andWhere(castJsonIfNeed('domain_names'), 'like', '%' + hostname + '%'),
.where("is_deleted", 0)
.andWhere(castJsonIfNeed("domain_names"), "like", `%${hostname}%`),
deadHostModel
.query()
.where('is_deleted', 0)
.andWhere(castJsonIfNeed('domain_names'), 'like', '%' + hostname + '%')
.where("is_deleted", 0)
.andWhere(castJsonIfNeed("domain_names"), "like", `%${hostname}%`),
];
return Promise.all(promises)
.then((promises_results) => {
let is_taken = false;
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[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]) {
// Redirection Hosts
if (
internalHost._checkHostnameRecordsTaken(
hostname,
promises_results[1],
ignore_type === "redirection" && ignore_id ? ignore_id : 0,
)
) {
is_taken = true;
}
}
if (promises_results[2]) {
// Dead Hosts
if (internalHost._checkHostnameRecordsTaken(hostname, promises_results[2], ignore_type === 'dead' && ignore_id ? ignore_id : 0)) {
is_taken = true;
}
if (promises_results[2]) {
// 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
};
});
return {
hostname: hostname,
is_taken: is_taken,
};
});
},
/**
* Private call only
*
* @param {String} hostname
* @param {Array} existing_rows
* @param {Integer} [ignore_id]
* @param {Array} existingRows
* @param {Integer} [ignoreId]
* @returns {Boolean}
*/
_checkHostnameRecordsTaken: function (hostname, existing_rows, ignore_id) {
let is_taken = false;
_checkHostnameRecordsTaken: (hostname, existingRows, ignoreId) => {
let isTaken = false;
if (existing_rows && existing_rows.length) {
existing_rows.map(function (existing_row) {
existing_row.domain_names.map(function (existing_hostname) {
if (existingRows?.length) {
existingRows.map((existingRow) => {
existingRow.domain_names.map((existingHostname) => {
// Does this domain match?
if (existing_hostname.toLowerCase() === hostname.toLowerCase()) {
if (!ignore_id || ignore_id !== existing_row.id) {
is_taken = true;
if (existingHostname.toLowerCase() === hostname.toLowerCase()) {
if (!ignoreId || ignoreId !== existingRow.id) {
isTaken = true;
}
}
return true;
});
return true;
});
}
return is_taken;
return isTaken;
},
/**
* Private call only
*
* @param {Array} hosts
* @param {Array} domain_names
* @param {Array} domainNames
* @returns {Array}
*/
_getHostsWithDomains: function (hosts, domain_names) {
let response = [];
_getHostsWithDomains: (hosts, domainNames) => {
const response = [];
if (hosts && hosts.length) {
hosts.map(function (host) {
let host_matches = false;
if (hosts?.length) {
hosts.map((host) => {
let hostMatches = false;
domain_names.map(function (domain_name) {
host.domain_names.map(function (host_domain_name) {
if (domain_name.toLowerCase() === host_domain_name.toLowerCase()) {
host_matches = true;
domainNames.map((domainName) => {
host.domain_names.map((hostDomainName) => {
if (domainName.toLowerCase() === hostDomainName.toLowerCase()) {
hostMatches = true;
}
return true;
});
return true;
});
if (host_matches) {
if (hostMatches) {
response.push(host);
}
return true;
});
}
return response;
}
},
};
module.exports = internalHost;
export default internalHost;

View File

@@ -1,45 +1,51 @@
const https = require('https');
const fs = require('fs');
const logger = require('../logger').ip_ranges;
const error = require('../lib/error');
const utils = require('../lib/utils');
const internalNginx = require('./nginx');
import fs from "node:fs";
import https from "node:https";
import { dirname } from "node:path";
import { fileURLToPath } from "node:url";
import errs from "../lib/error.js";
import utils from "../lib/utils.js";
import { ipRanges as logger } from "../logger.js";
import internalNginx from "./nginx.js";
const CLOUDFRONT_URL = 'https://ip-ranges.amazonaws.com/ip-ranges.json';
const CLOUDFARE_V4_URL = 'https://www.cloudflare.com/ips-v4';
const CLOUDFARE_V6_URL = 'https://www.cloudflare.com/ips-v6';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const CLOUDFRONT_URL = "https://ip-ranges.amazonaws.com/ip-ranges.json";
const CLOUDFARE_V4_URL = "https://www.cloudflare.com/ips-v4";
const CLOUDFARE_V6_URL = "https://www.cloudflare.com/ips-v6";
const regIpV4 = /^(\d+\.?){4}\/\d+/;
const regIpV6 = /^(([\da-fA-F]+)?:)+\/\d+/;
const internalIpRanges = {
interval_timeout: 1000 * 60 * 60 * 6, // 6 hours
interval: null,
interval_timeout: 1000 * 60 * 60 * 6, // 6 hours
interval: null,
interval_processing: false,
iteration_count: 0,
iteration_count: 0,
initTimer: () => {
logger.info('IP Ranges Renewal Timer initialized');
logger.info("IP Ranges Renewal Timer initialized");
internalIpRanges.interval = setInterval(internalIpRanges.fetch, internalIpRanges.interval_timeout);
},
fetchUrl: (url) => {
return new Promise((resolve, reject) => {
logger.info('Fetching ' + url);
return https.get(url, (res) => {
res.setEncoding('utf8');
let raw_data = '';
res.on('data', (chunk) => {
raw_data += chunk;
});
logger.info(`Fetching ${url}`);
return https
.get(url, (res) => {
res.setEncoding("utf8");
let raw_data = "";
res.on("data", (chunk) => {
raw_data += chunk;
});
res.on('end', () => {
resolve(raw_data);
res.on("end", () => {
resolve(raw_data);
});
})
.on("error", (err) => {
reject(err);
});
}).on('error', (err) => {
reject(err);
});
});
},
@@ -49,27 +55,30 @@ const internalIpRanges = {
fetch: () => {
if (!internalIpRanges.interval_processing) {
internalIpRanges.interval_processing = true;
logger.info('Fetching IP Ranges from online services...');
logger.info("Fetching IP Ranges from online services...");
let ip_ranges = [];
return internalIpRanges.fetchUrl(CLOUDFRONT_URL)
return internalIpRanges
.fetchUrl(CLOUDFRONT_URL)
.then((cloudfront_data) => {
let data = JSON.parse(cloudfront_data);
const data = JSON.parse(cloudfront_data);
if (data && typeof data.prefixes !== 'undefined') {
if (data && typeof data.prefixes !== "undefined") {
data.prefixes.map((item) => {
if (item.service === 'CLOUDFRONT') {
if (item.service === "CLOUDFRONT") {
ip_ranges.push(item.ip_prefix);
}
return true;
});
}
if (data && typeof data.ipv6_prefixes !== 'undefined') {
if (data && typeof data.ipv6_prefixes !== "undefined") {
data.ipv6_prefixes.map((item) => {
if (item.service === 'CLOUDFRONT') {
if (item.service === "CLOUDFRONT") {
ip_ranges.push(item.ipv6_prefix);
}
return true;
});
}
})
@@ -77,38 +86,38 @@ const internalIpRanges = {
return internalIpRanges.fetchUrl(CLOUDFARE_V4_URL);
})
.then((cloudfare_data) => {
let items = cloudfare_data.split('\n').filter((line) => regIpV4.test(line));
ip_ranges = [... ip_ranges, ... items];
const items = cloudfare_data.split("\n").filter((line) => regIpV4.test(line));
ip_ranges = [...ip_ranges, ...items];
})
.then(() => {
return internalIpRanges.fetchUrl(CLOUDFARE_V6_URL);
})
.then((cloudfare_data) => {
let items = cloudfare_data.split('\n').filter((line) => regIpV6.test(line));
ip_ranges = [... ip_ranges, ... items];
const items = cloudfare_data.split("\n").filter((line) => regIpV6.test(line));
ip_ranges = [...ip_ranges, ...items];
})
.then(() => {
let clean_ip_ranges = [];
const clean_ip_ranges = [];
ip_ranges.map((range) => {
if (range) {
clean_ip_ranges.push(range);
}
return true;
});
return internalIpRanges.generateConfig(clean_ip_ranges)
.then(() => {
if (internalIpRanges.iteration_count) {
// Reload nginx
return internalNginx.reload();
}
});
return internalIpRanges.generateConfig(clean_ip_ranges).then(() => {
if (internalIpRanges.iteration_count) {
// Reload nginx
return internalNginx.reload();
}
});
})
.then(() => {
internalIpRanges.interval_processing = false;
internalIpRanges.iteration_count++;
})
.catch((err) => {
logger.error(err.message);
logger.fatal(err.message);
internalIpRanges.interval_processing = false;
});
}
@@ -122,26 +131,26 @@ const internalIpRanges = {
const renderEngine = utils.getRenderEngine();
return new Promise((resolve, reject) => {
let template = null;
let filename = '/etc/nginx/conf.d/include/ip_ranges.conf';
const filename = "/etc/nginx/conf.d/include/ip_ranges.conf";
try {
template = fs.readFileSync(__dirname + '/../templates/ip_ranges.conf', {encoding: 'utf8'});
template = fs.readFileSync(`${__dirname}/../templates/ip_ranges.conf`, { encoding: "utf8" });
} catch (err) {
reject(new error.ConfigurationError(err.message));
reject(new errs.ConfigurationError(err.message));
return;
}
renderEngine
.parseAndRender(template, {ip_ranges: ip_ranges})
.parseAndRender(template, { ip_ranges: ip_ranges })
.then((config_text) => {
fs.writeFileSync(filename, config_text, {encoding: 'utf8'});
fs.writeFileSync(filename, config_text, { encoding: "utf8" });
resolve(true);
})
.catch((err) => {
logger.warn('Could not write ' + filename + ':', err.message);
reject(new error.ConfigurationError(err.message));
logger.warn(`Could not write ${filename}: ${err.message}`);
reject(new errs.ConfigurationError(err.message));
});
});
}
},
};
module.exports = internalIpRanges;
export default internalIpRanges;

View File

@@ -1,12 +1,15 @@
const _ = require('lodash');
const fs = require('node:fs');
const logger = require('../logger').nginx;
const config = require('../lib/config');
const utils = require('../lib/utils');
const error = require('../lib/error');
import fs from "node:fs";
import { dirname } from "node:path";
import { fileURLToPath } from "node:url";
import _ from "lodash";
import errs from "../lib/error.js";
import utils from "../lib/utils.js";
import { nginx as logger } from "../logger.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const internalNginx = {
/**
* This will:
* - test the nginx config first to make sure it's OK
@@ -24,7 +27,8 @@ const internalNginx = {
configure: (model, host_type, host) => {
let combined_meta = {};
return internalNginx.test()
return internalNginx
.test()
.then(() => {
// Nginx is OK
// We're deleting this config regardless.
@@ -37,20 +41,18 @@ const internalNginx = {
})
.then(() => {
// Test nginx again and update meta with result
return internalNginx.test()
return internalNginx
.test()
.then(() => {
// nginx is ok
combined_meta = _.assign({}, host.meta, {
nginx_online: true,
nginx_err: null
nginx_err: null,
});
return model
.query()
.where('id', host.id)
.patch({
meta: combined_meta
});
return model.query().where("id", host.id).patch({
meta: combined_meta,
});
})
.catch((err) => {
// Remove the error_log line because it's a docker-ism false positive that doesn't need to be reported.
@@ -58,28 +60,27 @@ const internalNginx = {
// nginx: [alert] could not open error log file: open() "/var/log/nginx/error.log" failed (6: No such device or address)
const valid_lines = [];
const err_lines = err.message.split('\n');
const err_lines = err.message.split("\n");
err_lines.map((line) => {
if (line.indexOf('/var/log/nginx/error.log') === -1) {
if (line.indexOf("/var/log/nginx/error.log") === -1) {
valid_lines.push(line);
}
return true;
});
if (config.debug()) {
logger.error('Nginx test failed:', valid_lines.join('\n'));
}
logger.debug("Nginx test failed:", valid_lines.join("\n"));
// config is bad, update meta and delete config
combined_meta = _.assign({}, host.meta, {
nginx_online: false,
nginx_err: valid_lines.join('\n')
nginx_err: valid_lines.join("\n"),
});
return model
.query()
.where('id', host.id)
.where("id", host.id)
.patch({
meta: combined_meta
meta: combined_meta,
})
.then(() => {
internalNginx.renameConfigAsError(host_type, host);
@@ -101,22 +102,18 @@ const internalNginx = {
* @returns {Promise}
*/
test: () => {
if (config.debug()) {
logger.info('Testing Nginx configuration');
}
return utils.execFile('/usr/sbin/nginx', ['-t', '-g', 'error_log off;']);
logger.debug("Testing Nginx configuration");
return utils.execFile("/usr/sbin/nginx", ["-t", "-g", "error_log off;"]);
},
/**
* @returns {Promise}
*/
reload: () => {
return internalNginx.test()
.then(() => {
logger.info('Reloading Nginx');
return utils.execFile('/usr/sbin/nginx', ['-s', 'reload']);
});
return internalNginx.test().then(() => {
logger.info("Reloading Nginx");
return utils.execFile("/usr/sbin/nginx", ["-s", "reload"]);
});
},
/**
@@ -125,8 +122,8 @@ const internalNginx = {
* @returns {String}
*/
getConfigName: (host_type, host_id) => {
if (host_type === 'default') {
return '/data/nginx/default_host/site.conf';
if (host_type === "default") {
return "/data/nginx/default_host/site.conf";
}
return `/data/nginx/${internalNginx.getFileFriendlyHostType(host_type)}/${host_id}.conf`;
},
@@ -141,38 +138,45 @@ const internalNginx = {
let template;
try {
template = fs.readFileSync(`${__dirname}/../templates/_location.conf`, {encoding: 'utf8'});
template = fs.readFileSync(`${__dirname}/../templates/_location.conf`, { encoding: "utf8" });
} catch (err) {
reject(new error.ConfigurationError(err.message));
reject(new errs.ConfigurationError(err.message));
return;
}
const renderEngine = utils.getRenderEngine();
let renderedLocations = '';
const renderEngine = utils.getRenderEngine();
let renderedLocations = "";
const locationRendering = async () => {
for (let i = 0; i < host.locations.length; i++) {
const locationCopy = Object.assign({}, {access_list_id: host.access_list_id}, {certificate_id: host.certificate_id},
{ssl_forced: host.ssl_forced}, {caching_enabled: host.caching_enabled}, {block_exploits: host.block_exploits},
{allow_websocket_upgrade: host.allow_websocket_upgrade}, {http2_support: host.http2_support},
{hsts_enabled: host.hsts_enabled}, {hsts_subdomains: host.hsts_subdomains}, {access_list: host.access_list},
{certificate: host.certificate}, host.locations[i]);
const locationCopy = Object.assign(
{},
{ access_list_id: host.access_list_id },
{ certificate_id: host.certificate_id },
{ ssl_forced: host.ssl_forced },
{ caching_enabled: host.caching_enabled },
{ block_exploits: host.block_exploits },
{ allow_websocket_upgrade: host.allow_websocket_upgrade },
{ http2_support: host.http2_support },
{ hsts_enabled: host.hsts_enabled },
{ hsts_subdomains: host.hsts_subdomains },
{ access_list: host.access_list },
{ certificate: host.certificate },
host.locations[i],
);
if (locationCopy.forward_host.indexOf('/') > -1) {
const splitted = locationCopy.forward_host.split('/');
if (locationCopy.forward_host.indexOf("/") > -1) {
const splitted = locationCopy.forward_host.split("/");
locationCopy.forward_host = splitted.shift();
locationCopy.forward_path = `/${splitted.join('/')}`;
locationCopy.forward_path = `/${splitted.join("/")}`;
}
// eslint-disable-next-line
renderedLocations += await renderEngine.parseAndRender(template, locationCopy);
}
};
locationRendering().then(() => resolve(renderedLocations));
});
},
@@ -183,23 +187,21 @@ const internalNginx = {
*/
generateConfig: (host_type, host_row) => {
// Prevent modifying the original object:
const host = JSON.parse(JSON.stringify(host_row));
const host = JSON.parse(JSON.stringify(host_row));
const nice_host_type = internalNginx.getFileFriendlyHostType(host_type);
if (config.debug()) {
logger.info(`Generating ${nice_host_type} Config:`, JSON.stringify(host, null, 2));
}
logger.debug(`Generating ${nice_host_type} Config:`, JSON.stringify(host, null, 2));
const renderEngine = utils.getRenderEngine();
return new Promise((resolve, reject) => {
let template = null;
let template = null;
const filename = internalNginx.getConfigName(nice_host_type, host.id);
try {
template = fs.readFileSync(`${__dirname}/../templates/${nice_host_type}.conf`, {encoding: 'utf8'});
template = fs.readFileSync(`${__dirname}/../templates/${nice_host_type}.conf`, { encoding: "utf8" });
} catch (err) {
reject(new error.ConfigurationError(err.message));
reject(new errs.ConfigurationError(err.message));
return;
}
@@ -207,27 +209,26 @@ const internalNginx = {
let origLocations;
// Manipulate the data a bit before sending it to the template
if (nice_host_type !== 'default') {
if (nice_host_type !== "default") {
host.use_default_location = true;
if (typeof host.advanced_config !== 'undefined' && host.advanced_config) {
if (typeof host.advanced_config !== "undefined" && host.advanced_config) {
host.use_default_location = !internalNginx.advancedConfigHasDefaultLocation(host.advanced_config);
}
}
if (host.locations) {
//logger.info ('host.locations = ' + JSON.stringify(host.locations, null, 2));
origLocations = [].concat(host.locations);
origLocations = [].concat(host.locations);
locationsPromise = internalNginx.renderLocations(host).then((renderedLocations) => {
host.locations = renderedLocations;
});
// Allow someone who is using / custom location path to use it, and skip the default / location
_.map(host.locations, (location) => {
if (location.path === '/') {
if (location.path === "/") {
host.use_default_location = false;
}
});
} else {
locationsPromise = Promise.resolve();
}
@@ -239,11 +240,8 @@ const internalNginx = {
renderEngine
.parseAndRender(template, host)
.then((config_text) => {
fs.writeFileSync(filename, config_text, {encoding: 'utf8'});
if (config.debug()) {
logger.success('Wrote config:', filename, config_text);
}
fs.writeFileSync(filename, config_text, { encoding: "utf8" });
logger.debug("Wrote config:", filename, config_text);
// Restore locations array
host.locations = origLocations;
@@ -251,11 +249,8 @@ const internalNginx = {
resolve(true);
})
.catch((err) => {
if (config.debug()) {
logger.warn(`Could not write ${filename}:`, err.message);
}
reject(new error.ConfigurationError(err.message));
logger.debug(`Could not write ${filename}:`, err.message);
reject(new errs.ConfigurationError(err.message));
});
});
});
@@ -270,20 +265,17 @@ const internalNginx = {
* @returns {Promise}
*/
generateLetsEncryptRequestConfig: (certificate) => {
if (config.debug()) {
logger.info('Generating LetsEncrypt Request Config:', certificate);
}
logger.debug("Generating LetsEncrypt Request Config:", certificate);
const renderEngine = utils.getRenderEngine();
return new Promise((resolve, reject) => {
let template = null;
let template = null;
const filename = `/data/nginx/temp/letsencrypt_${certificate.id}.conf`;
try {
template = fs.readFileSync(`${__dirname}/../templates/letsencrypt-request.conf`, {encoding: 'utf8'});
template = fs.readFileSync(`${__dirname}/../templates/letsencrypt-request.conf`, { encoding: "utf8" });
} catch (err) {
reject(new error.ConfigurationError(err.message));
reject(new errs.ConfigurationError(err.message));
return;
}
@@ -292,20 +284,13 @@ const internalNginx = {
renderEngine
.parseAndRender(template, certificate)
.then((config_text) => {
fs.writeFileSync(filename, config_text, {encoding: 'utf8'});
if (config.debug()) {
logger.success('Wrote config:', filename, config_text);
}
fs.writeFileSync(filename, config_text, { encoding: "utf8" });
logger.debug("Wrote config:", filename, config_text);
resolve(true);
})
.catch((err) => {
if (config.debug()) {
logger.warn(`Could not write ${filename}:`, err.message);
}
reject(new error.ConfigurationError(err.message));
logger.debug(`Could not write ${filename}:`, err.message);
reject(new errs.ConfigurationError(err.message));
});
});
},
@@ -320,7 +305,7 @@ const internalNginx = {
try {
fs.unlinkSync(filename);
} catch (err) {
logger.debug('Could not delete file:', JSON.stringify(err, null, 2));
logger.debug("Could not delete file:", JSON.stringify(err, null, 2));
}
},
@@ -330,7 +315,7 @@ const internalNginx = {
* @returns String
*/
getFileFriendlyHostType: (host_type) => {
return host_type.replace(/-/g, '_');
return host_type.replace(/-/g, "_");
},
/**
@@ -341,7 +326,7 @@ const internalNginx = {
*/
deleteLetsEncryptRequestConfig: (certificate) => {
const config_file = `/data/nginx/temp/letsencrypt_${certificate.id}.conf`;
return new Promise((resolve/*, reject*/) => {
return new Promise((resolve /*, reject*/) => {
internalNginx.deleteFile(config_file);
resolve();
});
@@ -354,10 +339,13 @@ const internalNginx = {
* @returns {Promise}
*/
deleteConfig: (host_type, host, delete_err_file) => {
const config_file = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id);
const config_file = internalNginx.getConfigName(
internalNginx.getFileFriendlyHostType(host_type),
typeof host === "undefined" ? 0 : host.id,
);
const config_file_err = `${config_file}.err`;
return new Promise((resolve/*, reject*/) => {
return new Promise((resolve /*, reject*/) => {
internalNginx.deleteFile(config_file);
if (delete_err_file) {
internalNginx.deleteFile(config_file_err);
@@ -372,10 +360,13 @@ const internalNginx = {
* @returns {Promise}
*/
renameConfigAsError: (host_type, host) => {
const config_file = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id);
const config_file = internalNginx.getConfigName(
internalNginx.getFileFriendlyHostType(host_type),
typeof host === "undefined" ? 0 : host.id,
);
const config_file_err = `${config_file}.err`;
return new Promise((resolve/*, reject*/) => {
return new Promise((resolve /*, reject*/) => {
fs.unlink(config_file, () => {
// ignore result, continue
fs.rename(config_file, config_file_err, () => {
@@ -395,6 +386,7 @@ const internalNginx = {
const promises = [];
hosts.map((host) => {
promises.push(internalNginx.generateConfig(host_type, host));
return true;
});
return Promise.all(promises);
@@ -409,6 +401,7 @@ const internalNginx = {
const promises = [];
hosts.map((host) => {
promises.push(internalNginx.deleteConfig(host_type, host, true));
return true;
});
return Promise.all(promises);
@@ -424,13 +417,13 @@ const internalNginx = {
* @returns {boolean}
*/
ipv6Enabled: () => {
if (typeof process.env.DISABLE_IPV6 !== 'undefined') {
if (typeof process.env.DISABLE_IPV6 !== "undefined") {
const disabled = process.env.DISABLE_IPV6.toLowerCase();
return !(disabled === 'on' || disabled === 'true' || disabled === '1' || disabled === 'yes');
return !(disabled === "on" || disabled === "true" || disabled === "1" || disabled === "yes");
}
return true;
}
},
};
module.exports = internalNginx;
export default internalNginx;

View File

@@ -1,107 +1,106 @@
const _ = require('lodash');
const error = require('../lib/error');
const utils = require('../lib/utils');
const proxyHostModel = require('../models/proxy_host');
const internalHost = require('./host');
const internalNginx = require('./nginx');
const internalAuditLog = require('./audit-log');
const internalCertificate = require('./certificate');
const {castJsonIfNeed} = require('../lib/helpers');
import _ from "lodash";
import errs from "../lib/error.js";
import { castJsonIfNeed } from "../lib/helpers.js";
import utils from "../lib/utils.js";
import proxyHostModel from "../models/proxy_host.js";
import internalAuditLog from "./audit-log.js";
import internalCertificate from "./certificate.js";
import internalHost from "./host.js";
import internalNginx from "./nginx.js";
function omissions () {
return ['is_deleted', 'owner.is_deleted'];
}
const omissions = () => {
return ["is_deleted", "owner.is_deleted"];
};
const internalProxyHost = {
/**
* @param {Access} access
* @param {Object} data
* @returns {Promise}
*/
create: (access, data) => {
let create_certificate = data.certificate_id === 'new';
let thisData = data;
const createCertificate = thisData.certificate_id === "new";
if (create_certificate) {
delete data.certificate_id;
if (createCertificate) {
delete thisData.certificate_id;
}
return access.can('proxy_hosts:create', data)
return access
.can("proxy_hosts:create", thisData)
.then(() => {
// Get a list of the domain names and check each of them against existing records
let domain_name_check_promises = [];
const domain_name_check_promises = [];
data.domain_names.map(function (domain_name) {
thisData.domain_names.map((domain_name) => {
domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name));
return true;
});
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');
}
});
return Promise.all(domain_name_check_promises).then((check_results) => {
check_results.map((result) => {
if (result.is_taken) {
throw new errs.ValidationError(`${result.hostname} is already in use`);
}
return true;
});
});
})
.then(() => {
// At this point the domains should have been checked
data.owner_user_id = access.token.getUserId(1);
data = internalHost.cleanSslHstsData(data);
thisData.owner_user_id = access.token.getUserId(1);
thisData = internalHost.cleanSslHstsData(thisData);
// Fix for db field not having a default value
// for this optional field.
if (typeof data.advanced_config === 'undefined') {
data.advanced_config = '';
if (typeof thisData.advanced_config === "undefined") {
thisData.advanced_config = "";
}
return proxyHostModel
.query()
.insertAndFetch(data)
.then(utils.omitRow(omissions()));
return proxyHostModel.query().insertAndFetch(thisData).then(utils.omitRow(omissions()));
})
.then((row) => {
if (create_certificate) {
return internalCertificate.createQuickCertificate(access, data)
if (createCertificate) {
return internalCertificate
.createQuickCertificate(access, thisData)
.then((cert) => {
// update host with cert id
return internalProxyHost.update(access, {
id: row.id,
certificate_id: cert.id
id: row.id,
certificate_id: cert.id,
});
})
.then(() => {
return row;
});
} else {
return row;
}
return row;
})
.then((row) => {
// re-fetch with cert
return internalProxyHost.get(access, {
id: row.id,
expand: ['certificate', 'owner', 'access_list.[clients,items]']
id: row.id,
expand: ["certificate", "owner", "access_list.[clients,items]"],
});
})
.then((row) => {
// Configure nginx
return internalNginx.configure(proxyHostModel, 'proxy_host', row)
.then(() => {
return row;
});
return internalNginx.configure(proxyHostModel, "proxy_host", row).then(() => {
return row;
});
})
.then((row) => {
// Audit log
data.meta = _.assign({}, data.meta || {}, row.meta);
thisData.meta = _.assign({}, thisData.meta || {}, row.meta);
// Add to audit log
return internalAuditLog.add(access, {
action: 'created',
object_type: 'proxy-host',
object_id: row.id,
meta: data
})
return internalAuditLog
.add(access, {
action: "created",
object_type: "proxy-host",
object_id: row.id,
meta: thisData,
})
.then(() => {
return row;
});
@@ -115,100 +114,110 @@ const internalProxyHost = {
* @return {Promise}
*/
update: (access, data) => {
let create_certificate = data.certificate_id === 'new';
let thisData = data;
const create_certificate = thisData.certificate_id === "new";
if (create_certificate) {
delete data.certificate_id;
delete thisData.certificate_id;
}
return access.can('proxy_hosts:update', data.id)
return access
.can("proxy_hosts:update", thisData.id)
.then((/*access_data*/) => {
// Get a list of the domain names and check each of them against existing records
let domain_name_check_promises = [];
const domain_name_check_promises = [];
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));
if (typeof thisData.domain_names !== "undefined") {
thisData.domain_names.map((domain_name) => {
return domain_name_check_promises.push(
internalHost.isHostnameTaken(domain_name, "proxy", thisData.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');
}
});
return Promise.all(domain_name_check_promises).then((check_results) => {
check_results.map((result) => {
if (result.is_taken) {
throw new errs.ValidationError(`${result.hostname} is already in use`);
}
return true;
});
});
}
})
.then(() => {
return internalProxyHost.get(access, {id: data.id});
return internalProxyHost.get(access, { id: thisData.id });
})
.then((row) => {
if (row.id !== data.id) {
if (row.id !== thisData.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);
throw new errs.InternalValidationError(
`Proxy Host could not be updated, IDs do not match: ${row.id} !== ${thisData.id}`,
);
}
if (create_certificate) {
return internalCertificate.createQuickCertificate(access, {
domain_names: data.domain_names || row.domain_names,
meta: _.assign({}, row.meta, data.meta)
})
return internalCertificate
.createQuickCertificate(access, {
domain_names: thisData.domain_names || row.domain_names,
meta: _.assign({}, row.meta, thisData.meta),
})
.then((cert) => {
// update host with cert id
data.certificate_id = cert.id;
thisData.certificate_id = cert.id;
})
.then(() => {
return row;
});
} else {
return row;
}
return row;
})
.then((row) => {
// Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here.
data = _.assign({}, {
domain_names: row.domain_names
}, data);
thisData = _.assign(
{},
{
domain_names: row.domain_names,
},
data,
);
data = internalHost.cleanSslHstsData(data, row);
thisData = internalHost.cleanSslHstsData(thisData, row);
return proxyHostModel
.query()
.where({id: data.id})
.patch(data)
.where({ id: thisData.id })
.patch(thisData)
.then(utils.omitRow(omissions()))
.then((saved_row) => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'updated',
object_type: 'proxy-host',
object_id: row.id,
meta: data
})
return internalAuditLog
.add(access, {
action: "updated",
object_type: "proxy-host",
object_id: row.id,
meta: thisData,
})
.then(() => {
return saved_row;
});
});
})
.then(() => {
return internalProxyHost.get(access, {
id: data.id,
expand: ['owner', 'certificate', 'access_list.[clients,items]']
})
return internalProxyHost
.get(access, {
id: thisData.id,
expand: ["owner", "certificate", "access_list.[clients,items]"],
})
.then((row) => {
if (!row.enabled) {
// No need to add nginx config if host is disabled
return row;
}
// Configure nginx
return internalNginx.configure(proxyHostModel, 'proxy_host', row)
.then((new_meta) => {
row.meta = new_meta;
row = internalHost.cleanRowCertificateMeta(row);
return _.omit(row, omissions());
});
return internalNginx.configure(proxyHostModel, "proxy_host", row).then((new_meta) => {
row.meta = new_meta;
return _.omit(internalHost.cleanRowCertificateMeta(row), omissions());
});
});
});
},
@@ -222,39 +231,38 @@ const internalProxyHost = {
* @return {Promise}
*/
get: (access, data) => {
if (typeof data === 'undefined') {
data = {};
}
const thisData = data || {};
return access.can('proxy_hosts:get', data.id)
return access
.can("proxy_hosts:get", thisData.id)
.then((access_data) => {
let query = proxyHostModel
const query = proxyHostModel
.query()
.where('is_deleted', 0)
.andWhere('id', data.id)
.allowGraph('[owner,access_list.[clients,items],certificate]')
.where("is_deleted", 0)
.andWhere("id", thisData.id)
.allowGraph("[owner,access_list.[clients,items],certificate]")
.first();
if (access_data.permission_visibility !== 'all') {
query.andWhere('owner_user_id', access.token.getUserId(1));
if (access_data.permission_visibility !== "all") {
query.andWhere("owner_user_id", access.token.getUserId(1));
}
if (typeof data.expand !== 'undefined' && data.expand !== null) {
query.withGraphFetched('[' + data.expand.join(', ') + ']');
if (typeof thisData.expand !== "undefined" && thisData.expand !== null) {
query.withGraphFetched(`[${thisData.expand.join(", ")}]`);
}
return query.then(utils.omitRow(omissions()));
})
.then((row) => {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
throw new errs.ItemNotFoundError(thisData.id);
}
row = internalHost.cleanRowCertificateMeta(row);
const thisRow = internalHost.cleanRowCertificateMeta(row);
// Custom omissions
if (typeof data.omit !== 'undefined' && data.omit !== null) {
row = _.omit(row, data.omit);
if (typeof thisData.omit !== "undefined" && thisData.omit !== null) {
return _.omit(row, thisData.omit);
}
return row;
return thisRow;
});
},
@@ -266,35 +274,35 @@ const internalProxyHost = {
* @returns {Promise}
*/
delete: (access, data) => {
return access.can('proxy_hosts:delete', data.id)
return access
.can("proxy_hosts:delete", data.id)
.then(() => {
return internalProxyHost.get(access, {id: data.id});
return internalProxyHost.get(access, { id: data.id });
})
.then((row) => {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
throw new errs.ItemNotFoundError(data.id);
}
return proxyHostModel
.query()
.where('id', row.id)
.where("id", row.id)
.patch({
is_deleted: 1
is_deleted: 1,
})
.then(() => {
// Delete Nginx Config
return internalNginx.deleteConfig('proxy_host', row)
.then(() => {
return internalNginx.reload();
});
return internalNginx.deleteConfig("proxy_host", row).then(() => {
return internalNginx.reload();
});
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'deleted',
object_type: 'proxy-host',
object_id: row.id,
meta: _.omit(row, omissions())
action: "deleted",
object_type: "proxy-host",
object_id: row.id,
meta: _.omit(row, omissions()),
});
});
})
@@ -311,39 +319,41 @@ const internalProxyHost = {
* @returns {Promise}
*/
enable: (access, data) => {
return access.can('proxy_hosts:update', data.id)
return access
.can("proxy_hosts:update", data.id)
.then(() => {
return internalProxyHost.get(access, {
id: data.id,
expand: ['certificate', 'owner', 'access_list']
id: data.id,
expand: ["certificate", "owner", "access_list"],
});
})
.then((row) => {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
} else if (row.enabled) {
throw new error.ValidationError('Host is already enabled');
throw new errs.ItemNotFoundError(data.id);
}
if (row.enabled) {
throw new errs.ValidationError("Host is already enabled");
}
row.enabled = 1;
return proxyHostModel
.query()
.where('id', row.id)
.where("id", row.id)
.patch({
enabled: 1
enabled: 1,
})
.then(() => {
// Configure nginx
return internalNginx.configure(proxyHostModel, 'proxy_host', row);
return internalNginx.configure(proxyHostModel, "proxy_host", row);
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'enabled',
object_type: 'proxy-host',
object_id: row.id,
meta: _.omit(row, omissions())
action: "enabled",
object_type: "proxy-host",
object_id: row.id,
meta: _.omit(row, omissions()),
});
});
})
@@ -360,39 +370,40 @@ const internalProxyHost = {
* @returns {Promise}
*/
disable: (access, data) => {
return access.can('proxy_hosts:update', data.id)
return access
.can("proxy_hosts:update", data.id)
.then(() => {
return internalProxyHost.get(access, {id: data.id});
return internalProxyHost.get(access, { id: data.id });
})
.then((row) => {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
} else if (!row.enabled) {
throw new error.ValidationError('Host is already disabled');
throw new errs.ItemNotFoundError(data.id);
}
if (!row.enabled) {
throw new errs.ValidationError("Host is already disabled");
}
row.enabled = 0;
return proxyHostModel
.query()
.where('id', row.id)
.where("id", row.id)
.patch({
enabled: 0
enabled: 0,
})
.then(() => {
// Delete Nginx Config
return internalNginx.deleteConfig('proxy_host', row)
.then(() => {
return internalNginx.reload();
});
return internalNginx.deleteConfig("proxy_host", row).then(() => {
return internalNginx.reload();
});
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'disabled',
object_type: 'proxy-host',
object_id: row.id,
meta: _.omit(row, omissions())
action: "disabled",
object_type: "proxy-host",
object_id: row.id,
meta: _.omit(row, omissions()),
});
});
})
@@ -410,34 +421,35 @@ const internalProxyHost = {
* @returns {Promise}
*/
getAll: (access, expand, search_query) => {
return access.can('proxy_hosts:list')
return access
.can("proxy_hosts:list")
.then((access_data) => {
let query = proxyHostModel
const query = proxyHostModel
.query()
.where('is_deleted', 0)
.groupBy('id')
.allowGraph('[owner,access_list,certificate]')
.orderBy(castJsonIfNeed('domain_names'), 'ASC');
.where("is_deleted", 0)
.groupBy("id")
.allowGraph("[owner,access_list,certificate]")
.orderBy(castJsonIfNeed("domain_names"), "ASC");
if (access_data.permission_visibility !== 'all') {
query.andWhere('owner_user_id', access.token.getUserId(1));
if (access_data.permission_visibility !== "all") {
query.andWhere("owner_user_id", access.token.getUserId(1));
}
// Query is used for searching
if (typeof search_query === 'string' && search_query.length > 0) {
if (typeof search_query === "string" && search_query.length > 0) {
query.where(function () {
this.where(castJsonIfNeed('domain_names'), 'like', `%${search_query}%`);
this.where(castJsonIfNeed("domain_names"), "like", `%${search_query}%`);
});
}
if (typeof expand !== 'undefined' && expand !== null) {
query.withGraphFetched('[' + expand.join(', ') + ']');
if (typeof expand !== "undefined" && expand !== null) {
query.withGraphFetched(`[${expand.join(", ")}]`);
}
return query.then(utils.omitRows(omissions()));
})
.then((rows) => {
if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) {
if (typeof expand !== "undefined" && expand !== null && expand.indexOf("certificate") !== -1) {
return internalHost.cleanAllRowsCertificateMeta(rows);
}
@@ -453,20 +465,16 @@ const internalProxyHost = {
* @returns {Promise}
*/
getCount: (user_id, visibility) => {
let query = proxyHostModel
.query()
.count('id as count')
.where('is_deleted', 0);
const query = proxyHostModel.query().count("id as count").where("is_deleted", 0);
if (visibility !== 'all') {
query.andWhere('owner_user_id', user_id);
if (visibility !== "all") {
query.andWhere("owner_user_id", user_id);
}
return query.first()
.then((row) => {
return parseInt(row.count, 10);
});
}
return query.first().then((row) => {
return Number.parseInt(row.count, 10);
});
},
};
module.exports = internalProxyHost;
export default internalProxyHost;

View File

@@ -1,73 +1,73 @@
const _ = require('lodash');
const error = require('../lib/error');
const utils = require('../lib/utils');
const redirectionHostModel = require('../models/redirection_host');
const internalHost = require('./host');
const internalNginx = require('./nginx');
const internalAuditLog = require('./audit-log');
const internalCertificate = require('./certificate');
const {castJsonIfNeed} = require('../lib/helpers');
import _ from "lodash";
import errs from "../lib/error.js";
import { castJsonIfNeed } from "../lib/helpers.js";
import utils from "../lib/utils.js";
import redirectionHostModel from "../models/redirection_host.js";
import internalAuditLog from "./audit-log.js";
import internalCertificate from "./certificate.js";
import internalHost from "./host.js";
import internalNginx from "./nginx.js";
function omissions () {
return ['is_deleted'];
}
const omissions = () => {
return ["is_deleted"];
};
const internalRedirectionHost = {
/**
* @param {Access} access
* @param {Object} data
* @returns {Promise}
*/
create: (access, data) => {
let create_certificate = data.certificate_id === 'new';
let thisData = data || {};
const createCertificate = thisData.certificate_id === "new";
if (create_certificate) {
delete data.certificate_id;
if (createCertificate) {
delete thisData.certificate_id;
}
return access.can('redirection_hosts:create', data)
return access
.can("redirection_hosts:create", thisData)
.then((/*access_data*/) => {
// Get a list of the domain names and check each of them against existing records
let domain_name_check_promises = [];
const domain_name_check_promises = [];
data.domain_names.map(function (domain_name) {
thisData.domain_names.map((domain_name) => {
domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name));
return true;
});
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');
}
});
return Promise.all(domain_name_check_promises).then((check_results) => {
check_results.map((result) => {
if (result.is_taken) {
throw new errs.ValidationError(`${result.hostname} is already in use`);
}
return true;
});
});
})
.then(() => {
// At this point the domains should have been checked
data.owner_user_id = access.token.getUserId(1);
data = internalHost.cleanSslHstsData(data);
thisData.owner_user_id = access.token.getUserId(1);
thisData = internalHost.cleanSslHstsData(thisData);
// Fix for db field not having a default value
// for this optional field.
if (typeof data.advanced_config === 'undefined') {
data.advanced_config = '';
if (typeof data.advanced_config === "undefined") {
data.advanced_config = "";
}
return redirectionHostModel
.query()
.insertAndFetch(data)
.then(utils.omitRow(omissions()));
return redirectionHostModel.query().insertAndFetch(thisData).then(utils.omitRow(omissions()));
})
.then((row) => {
if (create_certificate) {
return internalCertificate.createQuickCertificate(access, data)
if (createCertificate) {
return internalCertificate
.createQuickCertificate(access, thisData)
.then((cert) => {
// update host with cert id
return internalRedirectionHost.update(access, {
id: row.id,
certificate_id: cert.id
id: row.id,
certificate_id: cert.id,
});
})
.then(() => {
@@ -79,27 +79,27 @@ const internalRedirectionHost = {
.then((row) => {
// re-fetch with cert
return internalRedirectionHost.get(access, {
id: row.id,
expand: ['certificate', 'owner']
id: row.id,
expand: ["certificate", "owner"],
});
})
.then((row) => {
// Configure nginx
return internalNginx.configure(redirectionHostModel, 'redirection_host', row)
.then(() => {
return row;
});
return internalNginx.configure(redirectionHostModel, "redirection_host", row).then(() => {
return row;
});
})
.then((row) => {
data.meta = _.assign({}, data.meta || {}, row.meta);
thisData.meta = _.assign({}, thisData.meta || {}, row.meta);
// Add to audit log
return internalAuditLog.add(access, {
action: 'created',
object_type: 'redirection-host',
object_id: row.id,
meta: data
})
return internalAuditLog
.add(access, {
action: "created",
object_type: "redirection-host",
object_id: row.id,
meta: thisData,
})
.then(() => {
return row;
});
@@ -113,94 +113,107 @@ const internalRedirectionHost = {
* @return {Promise}
*/
update: (access, data) => {
let create_certificate = data.certificate_id === 'new';
let thisData = data || {};
const createCertificate = thisData.certificate_id === "new";
if (create_certificate) {
delete data.certificate_id;
if (createCertificate) {
delete thisData.certificate_id;
}
return access.can('redirection_hosts:update', data.id)
return access
.can("redirection_hosts:update", thisData.id)
.then((/*access_data*/) => {
// Get a list of the domain names and check each of them against existing records
let domain_name_check_promises = [];
const domain_name_check_promises = [];
if (typeof data.domain_names !== 'undefined') {
data.domain_names.map(function (domain_name) {
domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, 'redirection', data.id));
if (typeof thisData.domain_names !== "undefined") {
thisData.domain_names.map((domain_name) => {
domain_name_check_promises.push(
internalHost.isHostnameTaken(domain_name, "redirection", thisData.id),
);
return true;
});
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');
}
});
return Promise.all(domain_name_check_promises).then((check_results) => {
check_results.map((result) => {
if (result.is_taken) {
throw new errs.ValidationError(`${result.hostname} is already in use`);
}
return true;
});
});
}
})
.then(() => {
return internalRedirectionHost.get(access, {id: data.id});
return internalRedirectionHost.get(access, { id: thisData.id });
})
.then((row) => {
if (row.id !== data.id) {
if (row.id !== thisData.id) {
// Sanity check that something crazy hasn't happened
throw new error.InternalValidationError('Redirection Host could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
throw new errs.InternalValidationError(
`Redirection Host could not be updated, IDs do not match: ${row.id} !== ${thisData.id}`,
);
}
if (create_certificate) {
return internalCertificate.createQuickCertificate(access, {
domain_names: data.domain_names || row.domain_names,
meta: _.assign({}, row.meta, data.meta)
})
if (createCertificate) {
return internalCertificate
.createQuickCertificate(access, {
domain_names: thisData.domain_names || row.domain_names,
meta: _.assign({}, row.meta, thisData.meta),
})
.then((cert) => {
// update host with cert id
data.certificate_id = cert.id;
thisData.certificate_id = cert.id;
})
.then(() => {
return row;
});
} else {
return row;
}
return row;
})
.then((row) => {
// Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here.
data = _.assign({}, {
domain_names: row.domain_names
}, data);
thisData = _.assign(
{},
{
domain_names: row.domain_names,
},
thisData,
);
data = internalHost.cleanSslHstsData(data, row);
thisData = internalHost.cleanSslHstsData(thisData, row);
return redirectionHostModel
.query()
.where({id: data.id})
.patch(data)
.where({ id: thisData.id })
.patch(thisData)
.then((saved_row) => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'updated',
object_type: 'redirection-host',
object_id: row.id,
meta: data
})
return internalAuditLog
.add(access, {
action: "updated",
object_type: "redirection-host",
object_id: row.id,
meta: thisData,
})
.then(() => {
return _.omit(saved_row, omissions());
});
});
})
.then(() => {
return internalRedirectionHost.get(access, {
id: data.id,
expand: ['owner', 'certificate']
})
return internalRedirectionHost
.get(access, {
id: thisData.id,
expand: ["owner", "certificate"],
})
.then((row) => {
// Configure nginx
return internalNginx.configure(redirectionHostModel, 'redirection_host', row)
return internalNginx
.configure(redirectionHostModel, "redirection_host", row)
.then((new_meta) => {
row.meta = new_meta;
row = internalHost.cleanRowCertificateMeta(row);
return _.omit(row, omissions());
return _.omit(internalHost.cleanRowCertificateMeta(row), omissions());
});
});
});
@@ -215,39 +228,39 @@ const internalRedirectionHost = {
* @return {Promise}
*/
get: (access, data) => {
if (typeof data === 'undefined') {
data = {};
}
const thisData = data || {};
return access.can('redirection_hosts:get', data.id)
return access
.can("redirection_hosts:get", thisData.id)
.then((access_data) => {
let query = redirectionHostModel
const query = redirectionHostModel
.query()
.where('is_deleted', 0)
.andWhere('id', data.id)
.allowGraph('[owner,certificate]')
.where("is_deleted", 0)
.andWhere("id", thisData.id)
.allowGraph("[owner,certificate]")
.first();
if (access_data.permission_visibility !== 'all') {
query.andWhere('owner_user_id', access.token.getUserId(1));
if (access_data.permission_visibility !== "all") {
query.andWhere("owner_user_id", access.token.getUserId(1));
}
if (typeof data.expand !== 'undefined' && data.expand !== null) {
query.withGraphFetched('[' + data.expand.join(', ') + ']');
if (typeof thisData.expand !== "undefined" && thisData.expand !== null) {
query.withGraphFetched(`[${thisData.expand.join(", ")}]`);
}
return query.then(utils.omitRow(omissions()));
})
.then((row) => {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
let thisRow = row;
if (!thisRow || !thisRow.id) {
throw new errs.ItemNotFoundError(thisData.id);
}
row = internalHost.cleanRowCertificateMeta(row);
thisRow = internalHost.cleanRowCertificateMeta(thisRow);
// Custom omissions
if (typeof data.omit !== 'undefined' && data.omit !== null) {
row = _.omit(row, data.omit);
if (typeof thisData.omit !== "undefined" && thisData.omit !== null) {
return _.omit(thisRow, thisData.omit);
}
return row;
return thisRow;
});
},
@@ -259,35 +272,35 @@ const internalRedirectionHost = {
* @returns {Promise}
*/
delete: (access, data) => {
return access.can('redirection_hosts:delete', data.id)
return access
.can("redirection_hosts:delete", data.id)
.then(() => {
return internalRedirectionHost.get(access, {id: data.id});
return internalRedirectionHost.get(access, { id: data.id });
})
.then((row) => {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
throw new errs.ItemNotFoundError(data.id);
}
return redirectionHostModel
.query()
.where('id', row.id)
.where("id", row.id)
.patch({
is_deleted: 1
is_deleted: 1,
})
.then(() => {
// Delete Nginx Config
return internalNginx.deleteConfig('redirection_host', row)
.then(() => {
return internalNginx.reload();
});
return internalNginx.deleteConfig("redirection_host", row).then(() => {
return internalNginx.reload();
});
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'deleted',
object_type: 'redirection-host',
object_id: row.id,
meta: _.omit(row, omissions())
action: "deleted",
object_type: "redirection-host",
object_id: row.id,
meta: _.omit(row, omissions()),
});
});
})
@@ -304,39 +317,41 @@ const internalRedirectionHost = {
* @returns {Promise}
*/
enable: (access, data) => {
return access.can('redirection_hosts:update', data.id)
return access
.can("redirection_hosts:update", data.id)
.then(() => {
return internalRedirectionHost.get(access, {
id: data.id,
expand: ['certificate', 'owner']
id: data.id,
expand: ["certificate", "owner"],
});
})
.then((row) => {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
} else if (row.enabled) {
throw new error.ValidationError('Host is already enabled');
throw new errs.ItemNotFoundError(data.id);
}
if (row.enabled) {
throw new errs.ValidationError("Host is already enabled");
}
row.enabled = 1;
return redirectionHostModel
.query()
.where('id', row.id)
.where("id", row.id)
.patch({
enabled: 1
enabled: 1,
})
.then(() => {
// Configure nginx
return internalNginx.configure(redirectionHostModel, 'redirection_host', row);
return internalNginx.configure(redirectionHostModel, "redirection_host", row);
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'enabled',
object_type: 'redirection-host',
object_id: row.id,
meta: _.omit(row, omissions())
action: "enabled",
object_type: "redirection-host",
object_id: row.id,
meta: _.omit(row, omissions()),
});
});
})
@@ -353,39 +368,40 @@ const internalRedirectionHost = {
* @returns {Promise}
*/
disable: (access, data) => {
return access.can('redirection_hosts:update', data.id)
return access
.can("redirection_hosts:update", data.id)
.then(() => {
return internalRedirectionHost.get(access, {id: data.id});
return internalRedirectionHost.get(access, { id: data.id });
})
.then((row) => {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
} else if (!row.enabled) {
throw new error.ValidationError('Host is already disabled');
throw new errs.ItemNotFoundError(data.id);
}
if (!row.enabled) {
throw new errs.ValidationError("Host is already disabled");
}
row.enabled = 0;
return redirectionHostModel
.query()
.where('id', row.id)
.where("id", row.id)
.patch({
enabled: 0
enabled: 0,
})
.then(() => {
// Delete Nginx Config
return internalNginx.deleteConfig('redirection_host', row)
.then(() => {
return internalNginx.reload();
});
return internalNginx.deleteConfig("redirection_host", row).then(() => {
return internalNginx.reload();
});
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'disabled',
object_type: 'redirection-host',
object_id: row.id,
meta: _.omit(row, omissions())
action: "disabled",
object_type: "redirection-host",
object_id: row.id,
meta: _.omit(row, omissions()),
});
});
})
@@ -403,34 +419,35 @@ const internalRedirectionHost = {
* @returns {Promise}
*/
getAll: (access, expand, search_query) => {
return access.can('redirection_hosts:list')
return access
.can("redirection_hosts:list")
.then((access_data) => {
let query = redirectionHostModel
const query = redirectionHostModel
.query()
.where('is_deleted', 0)
.groupBy('id')
.allowGraph('[owner,certificate]')
.orderBy(castJsonIfNeed('domain_names'), 'ASC');
.where("is_deleted", 0)
.groupBy("id")
.allowGraph("[owner,certificate]")
.orderBy(castJsonIfNeed("domain_names"), "ASC");
if (access_data.permission_visibility !== 'all') {
query.andWhere('owner_user_id', access.token.getUserId(1));
if (access_data.permission_visibility !== "all") {
query.andWhere("owner_user_id", access.token.getUserId(1));
}
// Query is used for searching
if (typeof search_query === 'string' && search_query.length > 0) {
if (typeof search_query === "string" && search_query.length > 0) {
query.where(function () {
this.where(castJsonIfNeed('domain_names'), 'like', `%${search_query}%`);
this.where(castJsonIfNeed("domain_names"), "like", `%${search_query}%`);
});
}
if (typeof expand !== 'undefined' && expand !== null) {
query.withGraphFetched('[' + expand.join(', ') + ']');
if (typeof expand !== "undefined" && expand !== null) {
query.withGraphFetched(`[${expand.join(", ")}]`);
}
return query.then(utils.omitRows(omissions()));
})
.then((rows) => {
if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) {
if (typeof expand !== "undefined" && expand !== null && expand.indexOf("certificate") !== -1) {
return internalHost.cleanAllRowsCertificateMeta(rows);
}
@@ -446,20 +463,16 @@ const internalRedirectionHost = {
* @returns {Promise}
*/
getCount: (user_id, visibility) => {
let query = redirectionHostModel
.query()
.count('id as count')
.where('is_deleted', 0);
const query = redirectionHostModel.query().count("id as count").where("is_deleted", 0);
if (visibility !== 'all') {
query.andWhere('owner_user_id', user_id);
if (visibility !== "all") {
query.andWhere("owner_user_id", user_id);
}
return query.first()
.then((row) => {
return parseInt(row.count, 10);
});
}
return query.first().then((row) => {
return Number.parseInt(row.count, 10);
});
},
};
module.exports = internalRedirectionHost;
export default internalRedirectionHost;

View File

@@ -1,38 +1,37 @@
const internalProxyHost = require('./proxy-host');
const internalRedirectionHost = require('./redirection-host');
const internalDeadHost = require('./dead-host');
const internalStream = require('./stream');
import internalDeadHost from "./dead-host.js";
import internalProxyHost from "./proxy-host.js";
import internalRedirectionHost from "./redirection-host.js";
import internalStream from "./stream.js";
const internalReport = {
/**
* @param {Access} access
* @return {Promise}
*/
getHostsReport: (access) => {
return access.can('reports:hosts', 1)
return access
.can("reports:hosts", 1)
.then((access_data) => {
let user_id = access.token.getUserId(1);
const userId = access.token.getUserId(1);
let promises = [
internalProxyHost.getCount(user_id, access_data.visibility),
internalRedirectionHost.getCount(user_id, access_data.visibility),
internalStream.getCount(user_id, access_data.visibility),
internalDeadHost.getCount(user_id, access_data.visibility)
const promises = [
internalProxyHost.getCount(userId, access_data.visibility),
internalRedirectionHost.getCount(userId, access_data.visibility),
internalStream.getCount(userId, access_data.visibility),
internalDeadHost.getCount(userId, access_data.visibility),
];
return Promise.all(promises);
})
.then((counts) => {
return {
proxy: counts.shift(),
proxy: counts.shift(),
redirection: counts.shift(),
stream: counts.shift(),
dead: counts.shift()
stream: counts.shift(),
dead: counts.shift(),
};
});
}
},
};
module.exports = internalReport;
export default internalReport;

View File

@@ -1,10 +1,9 @@
const fs = require('fs');
const error = require('../lib/error');
const settingModel = require('../models/setting');
const internalNginx = require('./nginx');
import fs from "node:fs";
import errs from "../lib/error.js";
import settingModel from "../models/setting.js";
import internalNginx from "./nginx.js";
const internalSetting = {
/**
* @param {Access} access
* @param {Object} data
@@ -12,37 +11,38 @@ const internalSetting = {
* @return {Promise}
*/
update: (access, data) => {
return access.can('settings:update', data.id)
return access
.can("settings:update", data.id)
.then((/*access_data*/) => {
return internalSetting.get(access, {id: data.id});
return internalSetting.get(access, { id: data.id });
})
.then((row) => {
if (row.id !== data.id) {
// Sanity check that something crazy hasn't happened
throw new error.InternalValidationError('Setting could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
throw new errs.InternalValidationError(
`Setting could not be updated, IDs do not match: ${row.id} !== ${data.id}`,
);
}
return settingModel
.query()
.where({id: data.id})
.patch(data);
return settingModel.query().where({ id: data.id }).patch(data);
})
.then(() => {
return internalSetting.get(access, {
id: data.id
id: data.id,
});
})
.then((row) => {
if (row.id === 'default-site') {
if (row.id === "default-site") {
// write the html if we need to
if (row.value === 'html') {
fs.writeFileSync('/data/nginx/default_www/index.html', row.meta.html, {encoding: 'utf8'});
if (row.value === "html") {
fs.writeFileSync("/data/nginx/default_www/index.html", row.meta.html, { encoding: "utf8" });
}
// Configure nginx
return internalNginx.deleteConfig('default')
return internalNginx
.deleteConfig("default")
.then(() => {
return internalNginx.generateConfig('default', row);
return internalNginx.generateConfig("default", row);
})
.then(() => {
return internalNginx.test();
@@ -54,7 +54,8 @@ const internalSetting = {
return row;
})
.catch((/*err*/) => {
internalNginx.deleteConfig('default')
internalNginx
.deleteConfig("default")
.then(() => {
return internalNginx.test();
})
@@ -63,12 +64,11 @@ const internalSetting = {
})
.then(() => {
// I'm being slack here I know..
throw new error.ValidationError('Could not reconfigure Nginx. Please check logs.');
throw new errs.ValidationError("Could not reconfigure Nginx. Please check logs.");
});
});
} else {
return row;
}
return row;
});
},
@@ -79,19 +79,16 @@ const internalSetting = {
* @return {Promise}
*/
get: (access, data) => {
return access.can('settings:get', data.id)
return access
.can("settings:get", data.id)
.then(() => {
return settingModel
.query()
.where('id', data.id)
.first();
return settingModel.query().where("id", data.id).first();
})
.then((row) => {
if (row) {
return row;
} else {
throw new error.ItemNotFoundError(data.id);
}
throw new errs.ItemNotFoundError(data.id);
});
},
@@ -102,15 +99,13 @@ const internalSetting = {
* @returns {*}
*/
getCount: (access) => {
return access.can('settings:list')
return access
.can("settings:list")
.then(() => {
return settingModel
.query()
.count('id as count')
.first();
return settingModel.query().count("id as count").first();
})
.then((row) => {
return parseInt(row.count, 10);
return Number.parseInt(row.count, 10);
});
},
@@ -121,13 +116,10 @@ const internalSetting = {
* @returns {Promise}
*/
getAll: (access) => {
return access.can('settings:list')
.then(() => {
return settingModel
.query()
.orderBy('description', 'ASC');
});
}
return access.can("settings:list").then(() => {
return settingModel.query().orderBy("description", "ASC");
});
},
};
module.exports = internalSetting;
export default internalSetting;

View File

@@ -1,88 +1,85 @@
const _ = require('lodash');
const error = require('../lib/error');
const utils = require('../lib/utils');
const streamModel = require('../models/stream');
const internalNginx = require('./nginx');
const internalAuditLog = require('./audit-log');
const internalCertificate = require('./certificate');
const internalHost = require('./host');
const {castJsonIfNeed} = require('../lib/helpers');
import _ from "lodash";
import errs from "../lib/error.js";
import { castJsonIfNeed } from "../lib/helpers.js";
import utils from "../lib/utils.js";
import streamModel from "../models/stream.js";
import internalAuditLog from "./audit-log.js";
import internalCertificate from "./certificate.js";
import internalHost from "./host.js";
import internalNginx from "./nginx.js";
function omissions () {
return ['is_deleted', 'owner.is_deleted', 'certificate.is_deleted'];
}
const omissions = () => {
return ["is_deleted", "owner.is_deleted", "certificate.is_deleted"];
};
const internalStream = {
/**
* @param {Access} access
* @param {Object} data
* @returns {Promise}
*/
create: (access, data) => {
const create_certificate = data.certificate_id === 'new';
const create_certificate = data.certificate_id === "new";
if (create_certificate) {
delete data.certificate_id;
}
return access.can('streams:create', data)
return access
.can("streams:create", data)
.then((/*access_data*/) => {
// TODO: At this point the existing ports should have been checked
data.owner_user_id = access.token.getUserId(1);
if (typeof data.meta === 'undefined') {
if (typeof data.meta === "undefined") {
data.meta = {};
}
// streams aren't routed by domain name so don't store domain names in the DB
let data_no_domains = structuredClone(data);
const data_no_domains = structuredClone(data);
delete data_no_domains.domain_names;
return streamModel
.query()
.insertAndFetch(data_no_domains)
.then(utils.omitRow(omissions()));
return streamModel.query().insertAndFetch(data_no_domains).then(utils.omitRow(omissions()));
})
.then((row) => {
if (create_certificate) {
return internalCertificate.createQuickCertificate(access, data)
return internalCertificate
.createQuickCertificate(access, data)
.then((cert) => {
// update host with cert id
return internalStream.update(access, {
id: row.id,
certificate_id: cert.id
id: row.id,
certificate_id: cert.id,
});
})
.then(() => {
return row;
});
} else {
return row;
}
return row;
})
.then((row) => {
// re-fetch with cert
return internalStream.get(access, {
id: row.id,
expand: ['certificate', 'owner']
id: row.id,
expand: ["certificate", "owner"],
});
})
.then((row) => {
// Configure nginx
return internalNginx.configure(streamModel, 'stream', row)
.then(() => {
return row;
});
return internalNginx.configure(streamModel, "stream", row).then(() => {
return row;
});
})
.then((row) => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'created',
object_type: 'stream',
object_id: row.id,
meta: data
})
return internalAuditLog
.add(access, {
action: "created",
object_type: "stream",
object_id: row.id,
meta: data,
})
.then(() => {
return row;
});
@@ -96,72 +93,78 @@ const internalStream = {
* @return {Promise}
*/
update: (access, data) => {
const create_certificate = data.certificate_id === 'new';
let thisData = data;
const create_certificate = thisData.certificate_id === "new";
if (create_certificate) {
delete data.certificate_id;
delete thisData.certificate_id;
}
return access.can('streams:update', data.id)
return access
.can("streams:update", thisData.id)
.then((/*access_data*/) => {
// TODO: at this point the existing streams should have been checked
return internalStream.get(access, {id: data.id});
return internalStream.get(access, { id: thisData.id });
})
.then((row) => {
if (row.id !== data.id) {
if (row.id !== thisData.id) {
// Sanity check that something crazy hasn't happened
throw new error.InternalValidationError('Stream could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
throw new errs.InternalValidationError(
`Stream could not be updated, IDs do not match: ${row.id} !== ${thisData.id}`,
);
}
if (create_certificate) {
return internalCertificate.createQuickCertificate(access, {
domain_names: data.domain_names || row.domain_names,
meta: _.assign({}, row.meta, data.meta)
})
return internalCertificate
.createQuickCertificate(access, {
domain_names: thisData.domain_names || row.domain_names,
meta: _.assign({}, row.meta, thisData.meta),
})
.then((cert) => {
// update host with cert id
data.certificate_id = cert.id;
thisData.certificate_id = cert.id;
})
.then(() => {
return row;
});
} else {
return row;
}
return row;
})
.then((row) => {
// Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here.
data = _.assign({}, {
domain_names: row.domain_names
}, data);
thisData = _.assign(
{},
{
domain_names: row.domain_names,
},
thisData,
);
return streamModel
.query()
.patchAndFetchById(row.id, data)
.patchAndFetchById(row.id, thisData)
.then(utils.omitRow(omissions()))
.then((saved_row) => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'updated',
object_type: 'stream',
object_id: row.id,
meta: data
})
return internalAuditLog
.add(access, {
action: "updated",
object_type: "stream",
object_id: row.id,
meta: thisData,
})
.then(() => {
return saved_row;
});
});
})
.then(() => {
return internalStream.get(access, {id: data.id, expand: ['owner', 'certificate']})
.then((row) => {
return internalNginx.configure(streamModel, 'stream', row)
.then((new_meta) => {
row.meta = new_meta;
row = internalHost.cleanRowCertificateMeta(row);
return _.omit(row, omissions());
});
return internalStream.get(access, { id: thisData.id, expand: ["owner", "certificate"] }).then((row) => {
return internalNginx.configure(streamModel, "stream", row).then((new_meta) => {
row.meta = new_meta;
return _.omit(internalHost.cleanRowCertificateMeta(row), omissions());
});
});
});
},
@@ -174,39 +177,39 @@ const internalStream = {
* @return {Promise}
*/
get: (access, data) => {
if (typeof data === 'undefined') {
data = {};
}
const thisData = data || {};
return access.can('streams:get', data.id)
return access
.can("streams:get", thisData.id)
.then((access_data) => {
let query = streamModel
const query = streamModel
.query()
.where('is_deleted', 0)
.andWhere('id', data.id)
.allowGraph('[owner,certificate]')
.where("is_deleted", 0)
.andWhere("id", thisData.id)
.allowGraph("[owner,certificate]")
.first();
if (access_data.permission_visibility !== 'all') {
query.andWhere('owner_user_id', access.token.getUserId(1));
if (access_data.permission_visibility !== "all") {
query.andWhere("owner_user_id", access.token.getUserId(1));
}
if (typeof data.expand !== 'undefined' && data.expand !== null) {
query.withGraphFetched('[' + data.expand.join(', ') + ']');
if (typeof thisData.expand !== "undefined" && thisData.expand !== null) {
query.withGraphFetched(`[${thisData.expand.join(", ")}]`);
}
return query.then(utils.omitRow(omissions()));
})
.then((row) => {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
let thisRow = row;
if (!thisRow || !thisRow.id) {
throw new errs.ItemNotFoundError(thisData.id);
}
row = internalHost.cleanRowCertificateMeta(row);
thisRow = internalHost.cleanRowCertificateMeta(thisRow);
// Custom omissions
if (typeof data.omit !== 'undefined' && data.omit !== null) {
row = _.omit(row, data.omit);
if (typeof thisData.omit !== "undefined" && thisData.omit !== null) {
return _.omit(thisRow, thisData.omit);
}
return row;
return thisRow;
});
},
@@ -218,35 +221,35 @@ const internalStream = {
* @returns {Promise}
*/
delete: (access, data) => {
return access.can('streams:delete', data.id)
return access
.can("streams:delete", data.id)
.then(() => {
return internalStream.get(access, {id: data.id});
return internalStream.get(access, { id: data.id });
})
.then((row) => {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
throw new errs.ItemNotFoundError(data.id);
}
return streamModel
.query()
.where('id', row.id)
.where("id", row.id)
.patch({
is_deleted: 1
is_deleted: 1,
})
.then(() => {
// Delete Nginx Config
return internalNginx.deleteConfig('stream', row)
.then(() => {
return internalNginx.reload();
});
return internalNginx.deleteConfig("stream", row).then(() => {
return internalNginx.reload();
});
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'deleted',
object_type: 'stream',
object_id: row.id,
meta: _.omit(row, omissions())
action: "deleted",
object_type: "stream",
object_id: row.id,
meta: _.omit(row, omissions()),
});
});
})
@@ -263,39 +266,41 @@ const internalStream = {
* @returns {Promise}
*/
enable: (access, data) => {
return access.can('streams:update', data.id)
return access
.can("streams:update", data.id)
.then(() => {
return internalStream.get(access, {
id: data.id,
expand: ['certificate', 'owner']
id: data.id,
expand: ["certificate", "owner"],
});
})
.then((row) => {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
} else if (row.enabled) {
throw new error.ValidationError('Stream is already enabled');
throw new errs.ItemNotFoundError(data.id);
}
if (row.enabled) {
throw new errs.ValidationError("Stream is already enabled");
}
row.enabled = 1;
return streamModel
.query()
.where('id', row.id)
.where("id", row.id)
.patch({
enabled: 1
enabled: 1,
})
.then(() => {
// Configure nginx
return internalNginx.configure(streamModel, 'stream', row);
return internalNginx.configure(streamModel, "stream", row);
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'enabled',
object_type: 'stream',
object_id: row.id,
meta: _.omit(row, omissions())
action: "enabled",
object_type: "stream",
object_id: row.id,
meta: _.omit(row, omissions()),
});
});
})
@@ -312,39 +317,40 @@ const internalStream = {
* @returns {Promise}
*/
disable: (access, data) => {
return access.can('streams:update', data.id)
return access
.can("streams:update", data.id)
.then(() => {
return internalStream.get(access, {id: data.id});
return internalStream.get(access, { id: data.id });
})
.then((row) => {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
} else if (!row.enabled) {
throw new error.ValidationError('Stream is already disabled');
throw new errs.ItemNotFoundError(data.id);
}
if (!row.enabled) {
throw new errs.ValidationError("Stream is already disabled");
}
row.enabled = 0;
return streamModel
.query()
.where('id', row.id)
.where("id", row.id)
.patch({
enabled: 0
enabled: 0,
})
.then(() => {
// Delete Nginx Config
return internalNginx.deleteConfig('stream', row)
.then(() => {
return internalNginx.reload();
});
return internalNginx.deleteConfig("stream", row).then(() => {
return internalNginx.reload();
});
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'disabled',
object_type: 'stream-host',
object_id: row.id,
meta: _.omit(row, omissions())
action: "disabled",
object_type: "stream-host",
object_id: row.id,
meta: _.omit(row, omissions()),
});
});
})
@@ -362,34 +368,35 @@ const internalStream = {
* @returns {Promise}
*/
getAll: (access, expand, search_query) => {
return access.can('streams:list')
return access
.can("streams:list")
.then((access_data) => {
const query = streamModel
.query()
.where('is_deleted', 0)
.groupBy('id')
.allowGraph('[owner,certificate]')
.orderBy('incoming_port', 'ASC');
.where("is_deleted", 0)
.groupBy("id")
.allowGraph("[owner,certificate]")
.orderBy("incoming_port", "ASC");
if (access_data.permission_visibility !== 'all') {
query.andWhere('owner_user_id', access.token.getUserId(1));
if (access_data.permission_visibility !== "all") {
query.andWhere("owner_user_id", access.token.getUserId(1));
}
// Query is used for searching
if (typeof search_query === 'string' && search_query.length > 0) {
if (typeof search_query === "string" && search_query.length > 0) {
query.where(function () {
this.where(castJsonIfNeed('incoming_port'), 'like', `%${search_query}%`);
this.where(castJsonIfNeed("incoming_port"), "like", `%${search_query}%`);
});
}
if (typeof expand !== 'undefined' && expand !== null) {
query.withGraphFetched('[' + expand.join(', ') + ']');
if (typeof expand !== "undefined" && expand !== null) {
query.withGraphFetched(`[${expand.join(", ")}]`);
}
return query.then(utils.omitRows(omissions()));
})
.then((rows) => {
if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) {
if (typeof expand !== "undefined" && expand !== null && expand.indexOf("certificate") !== -1) {
return internalHost.cleanAllRowsCertificateMeta(rows);
}
@@ -405,20 +412,16 @@ const internalStream = {
* @returns {Promise}
*/
getCount: (user_id, visibility) => {
const query = streamModel
.query()
.count('id AS count')
.where('is_deleted', 0);
const query = streamModel.query().count("id AS count").where("is_deleted", 0);
if (visibility !== 'all') {
query.andWhere('owner_user_id', user_id);
if (visibility !== "all") {
query.andWhere("owner_user_id", user_id);
}
return query.first()
.then((row) => {
return parseInt(row.count, 10);
});
}
return query.first().then((row) => {
return Number.parseInt(row.count, 10);
});
},
};
module.exports = internalStream;
export default internalStream;

View File

@@ -1,14 +1,14 @@
const _ = require('lodash');
const error = require('../lib/error');
const userModel = require('../models/user');
const authModel = require('../models/auth');
const helpers = require('../lib/helpers');
const TokenModel = require('../models/token');
import _ from "lodash";
import errs from "../lib/error.js";
import { parseDatePeriod } from "../lib/helpers.js";
import authModel from "../models/auth.js";
import TokenModel from "../models/token.js";
import userModel from "../models/user.js";
const ERROR_MESSAGE_INVALID_AUTH = 'Invalid email or password';
module.exports = {
const ERROR_MESSAGE_INVALID_AUTH = "Invalid email or password";
const ERROR_MESSAGE_INVALID_AUTH_I18N = "error.invalid-auth";
export default {
/**
* @param {Object} data
* @param {String} data.identity
@@ -19,68 +19,65 @@ module.exports = {
* @returns {Promise}
*/
getTokenFromEmail: (data, issuer) => {
let Token = new TokenModel();
const Token = TokenModel();
data.scope = data.scope || 'user';
data.expiry = data.expiry || '1d';
data.scope = data.scope || "user";
data.expiry = data.expiry || "1d";
return userModel
.query()
.where('email', data.identity.toLowerCase().trim())
.andWhere('is_deleted', 0)
.andWhere('is_disabled', 0)
.where("email", data.identity.toLowerCase().trim())
.andWhere("is_deleted", 0)
.andWhere("is_disabled", 0)
.first()
.then((user) => {
if (user) {
// Get auth
return authModel
.query()
.where('user_id', '=', user.id)
.where('type', '=', 'password')
.where("user_id", "=", user.id)
.where("type", "=", "password")
.first()
.then((auth) => {
if (auth) {
return auth.verifyPassword(data.secret)
.then((valid) => {
if (valid) {
if (data.scope !== 'user' && _.indexOf(user.roles, data.scope) === -1) {
// The scope requested doesn't exist as a role against the user,
// you shall not pass.
throw new error.AuthError('Invalid scope: ' + data.scope);
}
// Create a moment of the expiry expression
let expiry = helpers.parseDatePeriod(data.expiry);
if (expiry === null) {
throw new error.AuthError('Invalid expiry time: ' + data.expiry);
}
return Token.create({
iss: issuer || 'api',
attrs: {
id: user.id
},
scope: [data.scope],
expiresIn: data.expiry
})
.then((signed) => {
return {
token: signed.token,
expires: expiry.toISOString()
};
});
} else {
throw new error.AuthError(ERROR_MESSAGE_INVALID_AUTH);
return auth.verifyPassword(data.secret).then((valid) => {
if (valid) {
if (data.scope !== "user" && _.indexOf(user.roles, data.scope) === -1) {
// The scope requested doesn't exist as a role against the user,
// you shall not pass.
throw new errs.AuthError(`Invalid scope: ${data.scope}`);
}
});
} else {
throw new error.AuthError(ERROR_MESSAGE_INVALID_AUTH);
// Create a moment of the expiry expression
const expiry = parseDatePeriod(data.expiry);
if (expiry === null) {
throw new errs.AuthError(`Invalid expiry time: ${data.expiry}`);
}
return Token.create({
iss: issuer || "api",
attrs: {
id: user.id,
},
scope: [data.scope],
expiresIn: data.expiry,
}).then((signed) => {
return {
token: signed.token,
expires: expiry.toISOString(),
};
});
}
throw new errs.AuthError(
ERROR_MESSAGE_INVALID_AUTH,
ERROR_MESSAGE_INVALID_AUTH_I18N,
);
});
}
throw new errs.AuthError(ERROR_MESSAGE_INVALID_AUTH);
});
} else {
throw new error.AuthError(ERROR_MESSAGE_INVALID_AUTH);
}
throw new errs.AuthError(ERROR_MESSAGE_INVALID_AUTH);
});
},
@@ -92,48 +89,45 @@ module.exports = {
* @returns {Promise}
*/
getFreshToken: (access, data) => {
let Token = new TokenModel();
const Token = TokenModel();
const thisData = data || {};
data = data || {};
data.expiry = data.expiry || '1d';
if (access && access.token.getUserId(0)) {
thisData.expiry = thisData.expiry || "1d";
if (access?.token.getUserId(0)) {
// Create a moment of the expiry expression
let expiry = helpers.parseDatePeriod(data.expiry);
const expiry = parseDatePeriod(thisData.expiry);
if (expiry === null) {
throw new error.AuthError('Invalid expiry time: ' + data.expiry);
throw new errs.AuthError(`Invalid expiry time: ${thisData.expiry}`);
}
let token_attrs = {
id: access.token.getUserId(0)
const token_attrs = {
id: access.token.getUserId(0),
};
// Only admins can request otherwise scoped tokens
let scope = access.token.get('scope');
if (data.scope && access.token.hasScope('admin')) {
scope = [data.scope];
let scope = access.token.get("scope");
if (thisData.scope && access.token.hasScope("admin")) {
scope = [thisData.scope];
if (data.scope === 'job-board' || data.scope === 'worker') {
if (thisData.scope === "job-board" || thisData.scope === "worker") {
token_attrs.id = 0;
}
}
return Token.create({
iss: 'api',
scope: scope,
attrs: token_attrs,
expiresIn: data.expiry
})
.then((signed) => {
return {
token: signed.token,
expires: expiry.toISOString()
};
});
} else {
throw new error.AssertionFailedError('Existing token contained invalid user data');
iss: "api",
scope: scope,
attrs: token_attrs,
expiresIn: thisData.expiry,
}).then((signed) => {
return {
token: signed.token,
expires: expiry.toISOString(),
};
});
}
throw new error.AssertionFailedError("Existing token contained invalid user data");
},
/**
@@ -141,24 +135,23 @@ module.exports = {
* @returns {Promise}
*/
getTokenFromUser: (user) => {
const expire = '1d';
const Token = new TokenModel();
const expiry = helpers.parseDatePeriod(expire);
const expire = "1d";
const Token = new TokenModel();
const expiry = parseDatePeriod(expire);
return Token.create({
iss: 'api',
iss: "api",
attrs: {
id: user.id
id: user.id,
},
scope: ['user'],
expiresIn: expire
})
.then((signed) => {
return {
token: signed.token,
expires: expiry.toISOString(),
user: user
};
});
}
scope: ["user"],
expiresIn: expire,
}).then((signed) => {
return {
token: signed.token,
expires: expiry.toISOString(),
user: user,
};
});
},
};

View File

@@ -1,43 +1,40 @@
const _ = require('lodash');
const error = require('../lib/error');
const utils = require('../lib/utils');
const userModel = require('../models/user');
const userPermissionModel = require('../models/user_permission');
const authModel = require('../models/auth');
const gravatar = require('gravatar');
const internalToken = require('./token');
const internalAuditLog = require('./audit-log');
import gravatar from "gravatar";
import _ from "lodash";
import errs from "../lib/error.js";
import utils from "../lib/utils.js";
import authModel from "../models/auth.js";
import userModel from "../models/user.js";
import userPermissionModel from "../models/user_permission.js";
import internalAuditLog from "./audit-log.js";
import internalToken from "./token.js";
function omissions () {
return ['is_deleted'];
const omissions = () => {
return ["is_deleted"];
}
const internalUser = {
/**
* @param {Access} access
* @param {Object} data
* @returns {Promise}
*/
create: (access, data) => {
let auth = data.auth || null;
const auth = data.auth || null;
delete data.auth;
data.avatar = data.avatar || '';
data.roles = data.roles || [];
data.avatar = data.avatar || "";
data.roles = data.roles || [];
if (typeof data.is_disabled !== 'undefined') {
if (typeof data.is_disabled !== "undefined") {
data.is_disabled = data.is_disabled ? 1 : 0;
}
return access.can('users:create', data)
return access
.can("users:create", data)
.then(() => {
data.avatar = gravatar.url(data.email, {default: 'mm'});
data.avatar = gravatar.url(data.email, { default: "mm" });
return userModel
.query()
.insertAndFetch(data)
.then(utils.omitRow(omissions()));
return userModel.query().insertAndFetch(data).then(utils.omitRow(omissions()));
})
.then((user) => {
if (auth) {
@@ -45,45 +42,45 @@ const internalUser = {
.query()
.insert({
user_id: user.id,
type: auth.type,
secret: auth.secret,
meta: {}
type: auth.type,
secret: auth.secret,
meta: {},
})
.then(() => {
return user;
});
} else {
return user;
}
return user;
})
.then((user) => {
// Create permissions row as well
let is_admin = data.roles.indexOf('admin') !== -1;
const 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',
certificates: 'manage'
user_id: user.id,
visibility: is_admin ? "all" : "user",
proxy_hosts: "manage",
redirection_hosts: "manage",
dead_hosts: "manage",
streams: "manage",
access_lists: "manage",
certificates: "manage",
})
.then(() => {
return internalUser.get(access, {id: user.id, expand: ['permissions']});
return internalUser.get(access, { id: user.id, expand: ["permissions"] });
});
})
.then((user) => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'created',
object_type: 'user',
object_id: user.id,
meta: user
})
return internalAuditLog
.add(access, {
action: "created",
object_type: "user",
object_id: user.id,
meta: user,
})
.then(() => {
return user;
});
@@ -99,62 +96,58 @@ const internalUser = {
* @return {Promise}
*/
update: (access, data) => {
if (typeof data.is_disabled !== 'undefined') {
if (typeof data.is_disabled !== "undefined") {
data.is_disabled = data.is_disabled ? 1 : 0;
}
return access.can('users:update', data.id)
return access
.can("users:update", data.id)
.then(() => {
// 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 internalUser.get(access, {id: data.id})
.then((user) => {
return internalUser.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();
// 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 internalUser.isEmailAvailable(data.email, data.id)
.then((available) => {
if (!available) {
throw new error.ValidationError('Email address already in use - ' + data.email);
}
return user;
});
}
if (user.email !== data.email) {
return internalUser.isEmailAvailable(data.email, data.id).then((available) => {
if (!available) {
throw new errs.ValidationError(`Email address already in use - ${data.email}`);
}
return user;
});
}
}
// No change to email:
return user;
});
// No change to email:
return user;
});
})
.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);
throw new errs.InternalValidationError(
`User could not be updated, IDs do not match: ${user.id} !== ${data.id}`,
);
}
data.avatar = gravatar.url(data.email || user.email, {default: 'mm'});
data.avatar = gravatar.url(data.email || user.email, { default: "mm" });
return userModel
.query()
.patchAndFetchById(user.id, data)
.then(utils.omitRow(omissions()));
return userModel.query().patchAndFetchById(user.id, data).then(utils.omitRow(omissions()));
})
.then(() => {
return internalUser.get(access, {id: data.id});
return internalUser.get(access, { id: data.id });
})
.then((user) => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'updated',
object_type: 'user',
object_id: user.id,
meta: data
})
return internalAuditLog
.add(access, {
action: "updated",
object_type: "user",
object_id: user.id,
meta: data,
})
.then(() => {
return user;
});
@@ -170,36 +163,35 @@ const internalUser = {
* @return {Promise}
*/
get: (access, data) => {
if (typeof data === 'undefined') {
data = {};
const thisData = data || {};
if (typeof thisData.id === "undefined" || !thisData.id) {
thisData.id = access.token.getUserId(0);
}
if (typeof data.id === 'undefined' || !data.id) {
data.id = access.token.getUserId(0);
}
return access.can('users:get', data.id)
return access
.can("users:get", thisData.id)
.then(() => {
let query = userModel
const query = userModel
.query()
.where('is_deleted', 0)
.andWhere('id', data.id)
.allowGraph('[permissions]')
.where("is_deleted", 0)
.andWhere("id", thisData.id)
.allowGraph("[permissions]")
.first();
if (typeof data.expand !== 'undefined' && data.expand !== null) {
query.withGraphFetched('[' + data.expand.join(', ') + ']');
if (typeof thisData.expand !== "undefined" && thisData.expand !== null) {
query.withGraphFetched(`[${thisData.expand.join(", ")}]`);
}
return query.then(utils.omitRow(omissions()));
})
.then((row) => {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
throw new errs.ItemNotFoundError(thisData.id);
}
// Custom omissions
if (typeof data.omit !== 'undefined' && data.omit !== null) {
row = _.omit(row, data.omit);
if (typeof thisData.omit !== "undefined" && thisData.omit !== null) {
return _.omit(row, thisData.omit);
}
return row;
});
@@ -213,20 +205,15 @@ const internalUser = {
* @param user_id
*/
isEmailAvailable: (email, user_id) => {
let query = userModel
.query()
.where('email', '=', email.toLowerCase().trim())
.where('is_deleted', 0)
.first();
const query = userModel.query().where("email", "=", email.toLowerCase().trim()).where("is_deleted", 0).first();
if (typeof user_id !== 'undefined') {
query.where('id', '!=', user_id);
if (typeof user_id !== "undefined") {
query.where("id", "!=", user_id);
}
return query
.then((user) => {
return !user;
});
return query.then((user) => {
return !user;
});
},
/**
@@ -237,33 +224,34 @@ const internalUser = {
* @returns {Promise}
*/
delete: (access, data) => {
return access.can('users:delete', data.id)
return access
.can("users:delete", data.id)
.then(() => {
return internalUser.get(access, {id: data.id});
return internalUser.get(access, { id: data.id });
})
.then((user) => {
if (!user) {
throw new error.ItemNotFoundError(data.id);
throw new errs.ItemNotFoundError(data.id);
}
// Make sure user can't delete themselves
if (user.id === access.token.getUserId(0)) {
throw new error.PermissionError('You cannot delete yourself.');
throw new errs.PermissionError("You cannot delete yourself.");
}
return userModel
.query()
.where('id', user.id)
.where("id", user.id)
.patch({
is_deleted: 1
is_deleted: 1,
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'deleted',
object_type: 'user',
object_id: user.id,
meta: _.omit(user, omissions())
action: "deleted",
object_type: "user",
object_id: user.id,
meta: _.omit(user, omissions()),
});
});
})
@@ -280,26 +268,26 @@ const internalUser = {
* @returns {*}
*/
getCount: (access, search_query) => {
return access.can('users:list')
return access
.can("users:list")
.then(() => {
let query = userModel
.query()
.count('id as count')
.where('is_deleted', 0)
.first();
const query = userModel.query().count("id as count").where("is_deleted", 0).first();
// Query is used for searching
if (typeof search_query === 'string') {
if (typeof search_query === "string") {
query.where(function () {
this.where('user.name', 'like', '%' + search_query + '%')
.orWhere('user.email', 'like', '%' + search_query + '%');
this.where("user.name", "like", `%${search_query}%`).orWhere(
"user.email",
"like",
`%${search_query}%`,
);
});
}
return query;
})
.then((row) => {
return parseInt(row.count, 10);
return Number.parseInt(row.count, 10);
});
},
@@ -312,29 +300,31 @@ const internalUser = {
* @returns {Promise}
*/
getAll: (access, expand, search_query) => {
return access.can('users:list')
.then(() => {
let query = userModel
.query()
.where('is_deleted', 0)
.groupBy('id')
.allowGraph('[permissions]')
.orderBy('name', 'ASC');
return access.can("users:list").then(() => {
const query = userModel
.query()
.where("is_deleted", 0)
.groupBy("id")
.allowGraph("[permissions]")
.orderBy("name", "ASC");
// Query is used for searching
if (typeof search_query === 'string') {
query.where(function () {
this.where('name', 'like', '%' + search_query + '%')
.orWhere('email', 'like', '%' + search_query + '%');
});
}
// Query is used for searching
if (typeof search_query === "string") {
query.where(function () {
this.where("name", "like", `%${search_query}%`).orWhere(
"email",
"like",
`%${search_query}%`,
);
});
}
if (typeof expand !== 'undefined' && expand !== null) {
query.withGraphFetched('[' + expand.join(', ') + ']');
}
if (typeof expand !== "undefined" && expand !== null) {
query.withGraphFetched(`[${expand.join(", ")}]`);
}
return query.then(utils.omitRows(omissions()));
});
return query.then(utils.omitRows(omissions()));
});
},
/**
@@ -345,8 +335,8 @@ const internalUser = {
getUserOmisionsByAccess: (access, id_requested) => {
let response = []; // Admin response
if (!access.token.hasScope('admin') && access.token.getUserId(0) !== id_requested) {
response = ['roles', 'is_deleted']; // Restricted response
if (!access.token.hasScope("admin") && access.token.getUserId(0) !== id_requested) {
response = ["roles", "is_deleted"]; // Restricted response
}
return response;
@@ -361,26 +351,30 @@ const internalUser = {
* @return {Promise}
*/
setPassword: (access, data) => {
return access.can('users:password', data.id)
return access
.can("users:password", data.id)
.then(() => {
return internalUser.get(access, {id: data.id});
return internalUser.get(access, { id: 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);
throw new errs.InternalValidationError(
`User could not be updated, IDs do not match: ${user.id} !== ${data.id}`,
);
}
if (user.id === access.token.getUserId(0)) {
// they're setting their own password. Make sure their current password is correct
if (typeof data.current === 'undefined' || !data.current) {
throw new error.ValidationError('Current password was not supplied');
if (typeof data.current === "undefined" || !data.current) {
throw new errs.ValidationError("Current password was not supplied");
}
return internalToken.getTokenFromEmail({
identity: user.email,
secret: data.current
})
return internalToken
.getTokenFromEmail({
identity: user.email,
secret: data.current,
})
.then(() => {
return user;
});
@@ -392,43 +386,36 @@ const internalUser = {
// Get auth, patch if it exists
return authModel
.query()
.where('user_id', user.id)
.andWhere('type', data.type)
.where("user_id", user.id)
.andWhere("type", data.type)
.first()
.then((existing_auth) => {
if (existing_auth) {
// patch
return authModel
.query()
.where('user_id', user.id)
.andWhere('type', data.type)
.patch({
type: data.type, // This is required for the model to encrypt on save
secret: data.secret
});
} else {
// insert
return authModel
.query()
.insert({
user_id: user.id,
type: data.type,
secret: data.secret,
meta: {}
});
return authModel.query().where("user_id", user.id).andWhere("type", data.type).patch({
type: data.type, // This is required for the model to encrypt on save
secret: data.secret,
});
}
// insert
return authModel.query().insert({
user_id: user.id,
type: data.type,
secret: data.secret,
meta: {},
});
})
.then(() => {
// Add to Audit Log
return internalAuditLog.add(access, {
action: 'updated',
object_type: 'user',
object_id: user.id,
meta: {
name: user.name,
action: "updated",
object_type: "user",
object_id: user.id,
meta: {
name: user.name,
password_changed: true,
auth_type: data.type
}
auth_type: data.type,
},
});
});
})
@@ -443,14 +430,17 @@ const internalUser = {
* @return {Promise}
*/
setPermissions: (access, data) => {
return access.can('users:permissions', data.id)
return access
.can("users:permissions", data.id)
.then(() => {
return internalUser.get(access, {id: data.id});
return internalUser.get(access, { id: 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);
throw new errs.InternalValidationError(
`User could not be updated, IDs do not match: ${user.id} !== ${data.id}`,
);
}
return user;
@@ -459,34 +449,30 @@ const internalUser = {
// Get perms row, patch if it exists
return userPermissionModel
.query()
.where('user_id', user.id)
.where("user_id", user.id)
.first()
.then((existing_auth) => {
if (existing_auth) {
// patch
return userPermissionModel
.query()
.where('user_id', user.id)
.patchAndFetchById(existing_auth.id, _.assign({user_id: user.id}, data));
} else {
// insert
return userPermissionModel
.query()
.insertAndFetch(_.assign({user_id: user.id}, data));
.where("user_id", user.id)
.patchAndFetchById(existing_auth.id, _.assign({ user_id: user.id }, data));
}
// insert
return userPermissionModel.query().insertAndFetch(_.assign({ user_id: user.id }, data));
})
.then((permissions) => {
// Add to Audit Log
return internalAuditLog.add(access, {
action: 'updated',
object_type: 'user',
object_id: user.id,
meta: {
name: user.name,
permissions: permissions
}
action: "updated",
object_type: "user",
object_id: user.id,
meta: {
name: user.name,
permissions: permissions,
},
});
});
})
.then(() => {
@@ -500,14 +486,15 @@ const internalUser = {
* @param {Integer} data.id
*/
loginAs: (access, data) => {
return access.can('users:loginas', data.id)
return access
.can("users:loginas", data.id)
.then(() => {
return internalUser.get(access, data);
})
.then((user) => {
return internalToken.getTokenFromUser(user);
});
}
},
};
module.exports = internalUser;
export default internalUser;