Compare commits

..

1 Commits

Author SHA1 Message Date
jc21
356eaa0691 Merge pull request #4653 from NginxProxyManager/develop
v2.12.6
2025-07-10 07:18:53 +10:00
476 changed files with 25313 additions and 15676 deletions

73
backend/.eslintrc.json Normal file
View File

@@ -0,0 +1,73 @@
{
"env": {
"node": true,
"es6": true
},
"extends": [
"eslint:recommended"
],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
},
"plugins": [
"align-assignments"
],
"rules": {
"arrow-parens": [
"error",
"always"
],
"indent": [
"error",
"tab"
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
],
"key-spacing": [
"error",
{
"align": "value"
}
],
"comma-spacing": [
"error",
{
"before": false,
"after": true
}
],
"func-call-spacing": [
"error",
"never"
],
"keyword-spacing": [
"error",
{
"before": true
}
],
"no-irregular-whitespace": "error",
"no-unused-expressions": 0,
"align-assignments/align-assignments": [
2,
{
"requiresOnly": false
}
]
}
}

11
backend/.prettierrc Normal file
View File

@@ -0,0 +1,11 @@
{
"printWidth": 320,
"tabWidth": 4,
"useTabs": true,
"semi": true,
"singleQuote": true,
"bracketSpacing": true,
"jsxBracketSameLine": true,
"trailingComma": "all",
"proseWrap": "always"
}

View File

@@ -1,12 +1,9 @@
import bodyParser from "body-parser"; const express = require('express');
import compression from "compression"; const bodyParser = require('body-parser');
import express from "express"; const fileUpload = require('express-fileupload');
import fileUpload from "express-fileupload"; const compression = require('compression');
import { isDebugMode } from "./lib/config.js"; const config = require('./lib/config');
import cors from "./lib/express/cors.js"; const log = require('./logger').express;
import jwt from "./lib/express/jwt.js";
import { express as logger } from "./logger.js";
import mainRoutes from "./routes/main.js";
/** /**
* App * App
@@ -23,70 +20,71 @@ app.use(compression());
* General Logging, BEFORE routes * General Logging, BEFORE routes
*/ */
app.disable("x-powered-by"); app.disable('x-powered-by');
app.enable("trust proxy", ["loopback", "linklocal", "uniquelocal"]); app.enable('trust proxy', ['loopback', 'linklocal', 'uniquelocal']);
app.enable("strict routing"); app.enable('strict routing');
// pretty print JSON when not live // pretty print JSON when not live
if (isDebugMode()) { if (config.debug()) {
app.set("json spaces", 2); app.set('json spaces', 2);
} }
// CORS for everything // CORS for everything
app.use(cors); app.use(require('./lib/express/cors'));
// General security/cache related headers + server header // General security/cache related headers + server header
app.use((_, res, next) => { app.use(function (req, res, next) {
let x_frame_options = "DENY"; let x_frame_options = 'DENY';
if (typeof process.env.X_FRAME_OPTIONS !== "undefined" && process.env.X_FRAME_OPTIONS) { if (typeof process.env.X_FRAME_OPTIONS !== 'undefined' && process.env.X_FRAME_OPTIONS) {
x_frame_options = process.env.X_FRAME_OPTIONS; x_frame_options = process.env.X_FRAME_OPTIONS;
} }
res.set({ res.set({
"X-XSS-Protection": "1; mode=block", 'X-XSS-Protection': '1; mode=block',
"X-Content-Type-Options": "nosniff", 'X-Content-Type-Options': 'nosniff',
"X-Frame-Options": x_frame_options, 'X-Frame-Options': x_frame_options,
"Cache-Control": "no-cache, no-store, max-age=0, must-revalidate", 'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
Pragma: "no-cache", Pragma: 'no-cache',
Expires: 0, Expires: 0
}); });
next(); next();
}); });
app.use(jwt()); app.use(require('./lib/express/jwt')());
app.use("/", mainRoutes); app.use('/', require('./routes/main'));
// production error handler // production error handler
// no stacktraces leaked to user // no stacktraces leaked to user
app.use((err, req, res, _) => { // eslint-disable-next-line
const payload = { app.use(function (err, req, res, next) {
let payload = {
error: { error: {
code: err.status, code: err.status,
message: err.public ? err.message : "Internal Error", message: err.public ? err.message : 'Internal Error'
}, }
}; };
if (typeof err.message_i18n !== "undefined") { if (config.debug() || (req.baseUrl + req.path).includes('nginx/certificates')) {
payload.error.message_i18n = err.message_i18n;
}
if (isDebugMode() || (req.baseUrl + req.path).includes("nginx/certificates")) {
payload.debug = { payload.debug = {
stack: typeof err.stack !== "undefined" && err.stack ? err.stack.split("\n") : null, stack: typeof err.stack !== 'undefined' && err.stack ? err.stack.split('\n') : null,
previous: err.previous, previous: err.previous
}; };
} }
// Not every error is worth logging - but this is good for now until it gets annoying. // Not every error is worth logging - but this is good for now until it gets annoying.
if (typeof err.stack !== "undefined" && err.stack) { if (typeof err.stack !== 'undefined' && err.stack) {
logger.debug(err.stack); if (config.debug()) {
if (typeof err.public === "undefined" || !err.public) { log.debug(err.stack);
logger.warn(err.message); } else if (typeof err.public == 'undefined' || !err.public) {
log.warn(err.message);
} }
} }
res.status(err.status || 500).send(payload); res
.status(err.status || 500)
.send(payload);
}); });
export default app; module.exports = app;

View File

@@ -1,91 +0,0 @@
{
"$schema": "https://biomejs.dev/schemas/2.2.4/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"ignoreUnknown": false,
"includes": [
"**/*.ts",
"**/*.tsx",
"**/*.js",
"**/*.jsx",
"!**/dist/**/*"
]
},
"formatter": {
"enabled": true,
"indentStyle": "tab",
"indentWidth": 4,
"lineWidth": 120,
"formatWithErrors": true
},
"assist": {
"actions": {
"source": {
"organizeImports": {
"level": "on",
"options": {
"groups": [
":BUN:",
":NODE:",
[
"npm:*",
"npm:*/**"
],
":PACKAGE_WITH_PROTOCOL:",
":URL:",
":PACKAGE:",
[
"/src/*",
"/src/**"
],
[
"/**"
],
[
"#*",
"#*/**"
],
":PATH:"
]
}
}
}
}
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"correctness": {
"useUniqueElementIds": "off"
},
"suspicious": {
"noExplicitAny": "off"
},
"performance": {
"noDelete": "off"
},
"nursery": "off",
"a11y": {
"useSemanticElements": "off",
"useValidAnchor": "off"
},
"style": {
"noParameterAssign": "error",
"useAsConstAssertion": "error",
"useDefaultParameterLast": "error",
"useEnumInitializers": "error",
"useSelfClosingElements": "error",
"useSingleVarDeclarator": "error",
"noUnusedTemplateLiteral": "error",
"useNumberNamespace": "error",
"noInferrableTypes": "error",
"noUselessElse": "error"
}
}
}
}

View File

@@ -1,19 +1,14 @@
import knex from "knex"; const config = require('./lib/config');
import {configGet, configHas} from "./lib/config.js";
const generateDbConfig = () => { if (!config.has('database')) {
if (!configHas("database")) { throw new Error('Database config does not exist! Please read the instructions: https://nginxproxymanager.com/setup/');
throw new Error(
"Database config does not exist! Please read the instructions: https://nginxproxymanager.com/setup/",
);
} }
const cfg = configGet("database"); function generateDbConfig() {
const cfg = config.get('database');
if (cfg.engine === "knex-native") { if (cfg.engine === 'knex-native') {
return cfg.knex; return cfg.knex;
} }
return { return {
client: cfg.engine, client: cfg.engine,
connection: { connection: {
@@ -21,12 +16,12 @@ const generateDbConfig = () => {
user: cfg.user, user: cfg.user,
password: cfg.password, password: cfg.password,
database: cfg.name, database: cfg.name,
port: cfg.port, port: cfg.port
}, },
migrations: { migrations: {
tableName: "migrations", tableName: 'migrations'
}, }
};
}; };
}
export default knex(generateDbConfig()); module.exports = require('knex')(generateDbConfig());

View File

@@ -1,47 +1,48 @@
#!/usr/bin/env node #!/usr/bin/env node
import app from "./app.js"; const schema = require('./schema');
import internalCertificate from "./internal/certificate.js"; const logger = require('./logger').global;
import internalIpRanges from "./internal/ip_ranges.js";
import { global as logger } from "./logger.js";
import { migrateUp } from "./migrate.js";
import { getCompiledSchema } from "./schema/index.js";
import setup from "./setup.js";
const IP_RANGES_FETCH_ENABLED = process.env.IP_RANGES_FETCH_ENABLED !== "false"; const IP_RANGES_FETCH_ENABLED = process.env.IP_RANGES_FETCH_ENABLED !== 'false';
async function appStart () { async function appStart () {
return migrateUp() const migrate = require('./migrate');
const setup = require('./setup');
const app = require('./app');
const internalCertificate = require('./internal/certificate');
const internalIpRanges = require('./internal/ip_ranges');
return migrate.latest()
.then(setup) .then(setup)
.then(getCompiledSchema) .then(schema.getCompiledSchema)
.then(() => { .then(() => {
if (!IP_RANGES_FETCH_ENABLED) { if (IP_RANGES_FETCH_ENABLED) {
logger.info("IP Ranges fetch is disabled by environment variable"); logger.info('IP Ranges fetch is enabled');
return;
}
logger.info("IP Ranges fetch is enabled");
return internalIpRanges.fetch().catch((err) => { return internalIpRanges.fetch().catch((err) => {
logger.error("IP Ranges fetch failed, continuing anyway:", err.message); logger.error('IP Ranges fetch failed, continuing anyway:', err.message);
}); });
} else {
logger.info('IP Ranges fetch is disabled by environment variable');
}
}) })
.then(() => { .then(() => {
internalCertificate.initTimer(); internalCertificate.initTimer();
internalIpRanges.initTimer(); internalIpRanges.initTimer();
const server = app.listen(3000, () => { const server = app.listen(3000, () => {
logger.info(`Backend PID ${process.pid} listening on port 3000 ...`); logger.info('Backend PID ' + process.pid + ' listening on port 3000 ...');
process.on("SIGTERM", () => { process.on('SIGTERM', () => {
logger.info(`PID ${process.pid} received SIGTERM`); logger.info('PID ' + process.pid + ' received SIGTERM');
server.close(() => { server.close(() => {
logger.info("Stopping."); logger.info('Stopping.');
process.exit(0); process.exit(0);
}); });
}); });
}); });
}) })
.catch((err) => { .catch((err) => {
logger.error(`Startup Error: ${err.message}`, err); logger.error(err.message, err);
setTimeout(appStart, 1000); setTimeout(appStart, 1000);
}); });
} }
@@ -49,6 +50,7 @@ async function appStart() {
try { try {
appStart(); appStart();
} catch (err) { } catch (err) {
logger.fatal(err); logger.error(err.message, err);
process.exit(1); process.exit(1);
} }

View File

@@ -1,94 +1,103 @@
import fs from "node:fs"; const _ = require('lodash');
import batchflow from "batchflow"; const fs = require('fs');
import _ from "lodash"; const batchflow = require('batchflow');
import errs from "../lib/error.js"; const logger = require('../logger').access;
import utils from "../lib/utils.js"; const error = require('../lib/error');
import { access as logger } from "../logger.js"; const utils = require('../lib/utils');
import accessListModel from "../models/access_list.js"; const accessListModel = require('../models/access_list');
import accessListAuthModel from "../models/access_list_auth.js"; const accessListAuthModel = require('../models/access_list_auth');
import accessListClientModel from "../models/access_list_client.js"; const accessListClientModel = require('../models/access_list_client');
import proxyHostModel from "../models/proxy_host.js"; const proxyHostModel = require('../models/proxy_host');
import internalAuditLog from "./audit-log.js"; const internalAuditLog = require('./audit-log');
import internalNginx from "./nginx.js"; const internalNginx = require('./nginx');
const omissions = () => { function omissions () {
return ["is_deleted"]; return ['is_deleted'];
}; }
const internalAccessList = { const internalAccessList = {
/** /**
* @param {Access} access * @param {Access} access
* @param {Object} data * @param {Object} data
* @returns {Promise} * @returns {Promise}
*/ */
create: async (access, data) => { create: (access, data) => {
await access.can("access_lists:create", data); return access.can('access_lists:create', data)
const row = await accessListModel .then((/*access_data*/) => {
return accessListModel
.query() .query()
.insertAndFetch({ .insertAndFetch({
name: data.name, name: data.name,
satisfy_any: data.satisfy_any, satisfy_any: data.satisfy_any,
pass_auth: data.pass_auth, pass_auth: data.pass_auth,
owner_user_id: access.token.getUserId(1), owner_user_id: access.token.getUserId(1)
}) })
.then(utils.omitRow(omissions())); .then(utils.omitRow(omissions()));
})
.then((row) => {
data.id = row.id; data.id = row.id;
const promises = []; let promises = [];
// Items
// Now add the items
data.items.map((item) => { data.items.map((item) => {
promises.push( promises.push(accessListAuthModel
accessListAuthModel.query().insert({ .query()
.insert({
access_list_id: row.id, access_list_id: row.id,
username: item.username, username: item.username,
password: item.password, password: item.password
}), })
); );
return true;
}); });
// Clients // Now add the clients
data.clients?.map((client) => { if (typeof data.clients !== 'undefined' && data.clients) {
promises.push( data.clients.map((client) => {
accessListClientModel.query().insert({ promises.push(accessListClientModel
.query()
.insert({
access_list_id: row.id, access_list_id: row.id,
address: client.address, address: client.address,
directive: client.directive, directive: client.directive
}), })
); );
return true;
}); });
await Promise.all(promises);
// re-fetch with expansions
const freshRow = await internalAccessList.get(
access,
{
id: data.id,
expand: ["owner", "items", "clients", "proxy_hosts.access_list.[clients,items]"],
},
true // skip masking
);
// Audit log
data.meta = _.assign({}, data.meta || {}, freshRow.meta);
await internalAccessList.build(freshRow);
if (Number.parseInt(freshRow.proxy_host_count, 10)) {
await internalNginx.bulkGenerateConfigs("proxy_host", freshRow.proxy_hosts);
} }
// Add to audit log return Promise.all(promises);
await internalAuditLog.add(access, { })
action: "created", .then(() => {
object_type: "access-list", // re-fetch with expansions
object_id: freshRow.id, return internalAccessList.get(access, {
meta: internalAccessList.maskItems(data), 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.maskItems(freshRow); return internalAccessList.build(row)
.then(() => {
if (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)
});
})
.then(() => {
return internalAccessList.maskItems(row);
});
});
}, },
/** /**
@@ -99,107 +108,129 @@ const internalAccessList = {
* @param {String} [data.items] * @param {String} [data.items]
* @return {Promise} * @return {Promise}
*/ */
update: async (access, data) => { update: (access, data) => {
await access.can("access_lists:update", data.id); return access.can('access_lists:update', data.id)
const row = await internalAccessList.get(access, { id: data.id }); .then((/*access_data*/) => {
return internalAccessList.get(access, {id: data.id});
})
.then((row) => {
if (row.id !== data.id) { if (row.id !== data.id) {
// Sanity check that something crazy hasn't happened // Sanity check that something crazy hasn't happened
throw new errs.InternalValidationError( throw new error.InternalValidationError('Access List could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
`Access List could not be updated, IDs do not match: ${row.id} !== ${data.id}`,
);
} }
})
.then(() => {
// patch name if specified // patch name if specified
if (typeof data.name !== "undefined" && data.name) { if (typeof data.name !== 'undefined' && data.name) {
await accessListModel.query().where({ id: data.id }).patch({ return accessListModel
.query()
.where({id: data.id})
.patch({
name: data.name, name: data.name,
satisfy_any: data.satisfy_any, satisfy_any: data.satisfy_any,
pass_auth: data.pass_auth, pass_auth: data.pass_auth,
}); });
} }
})
.then(() => {
// Check for items and add/update/remove them // Check for items and add/update/remove them
if (typeof data.items !== "undefined" && data.items) { if (typeof data.items !== 'undefined' && data.items) {
const promises = []; let promises = [];
const itemsToKeep = []; let items_to_keep = [];
data.items.map((item) => { data.items.map(function (item) {
if (item.password) { if (item.password) {
promises.push( promises.push(accessListAuthModel
accessListAuthModel.query().insert({ .query()
.insert({
access_list_id: data.id, access_list_id: data.id,
username: item.username, username: item.username,
password: item.password, password: item.password
}), })
); );
} else { } else {
// This was supplied with an empty password, which means keep it but don't change the password // This was supplied with an empty password, which means keep it but don't change the password
itemsToKeep.push(item.username); items_to_keep.push(item.username);
} }
return true;
}); });
const query = accessListAuthModel.query().delete().where("access_list_id", data.id); let query = accessListAuthModel
.query()
.delete()
.where('access_list_id', data.id);
if (itemsToKeep.length) { if (items_to_keep.length) {
query.andWhere("username", "NOT IN", itemsToKeep); query.andWhere('username', 'NOT IN', items_to_keep);
} }
await query; return query
.then(() => {
// Add new items // Add new items
if (promises.length) { if (promises.length) {
await Promise.all(promises); return Promise.all(promises);
} }
});
} }
})
.then(() => {
// Check for clients and add/update/remove them // Check for clients and add/update/remove them
if (typeof data.clients !== "undefined" && data.clients) { if (typeof data.clients !== 'undefined' && data.clients) {
const clientPromises = []; let promises = [];
data.clients.map((client) => {
data.clients.map(function (client) {
if (client.address) { if (client.address) {
clientPromises.push( promises.push(accessListClientModel
accessListClientModel.query().insert({ .query()
.insert({
access_list_id: data.id, access_list_id: data.id,
address: client.address, address: client.address,
directive: client.directive, directive: client.directive
}), })
); );
} }
return true;
}); });
const query = accessListClientModel.query().delete().where("access_list_id", data.id); let query = accessListClientModel
await query; .query()
// Add new clitens .delete()
if (clientPromises.length) { .where('access_list_id', data.id);
await Promise.all(clientPromises);
}
}
return query
.then(() => {
// Add new items
if (promises.length) {
return Promise.all(promises);
}
});
}
})
.then(() => {
// Add to audit log // Add to audit log
await internalAuditLog.add(access, { return internalAuditLog.add(access, {
action: "updated", action: 'updated',
object_type: "access-list", object_type: 'access-list',
object_id: data.id, object_id: data.id,
meta: internalAccessList.maskItems(data), meta: internalAccessList.maskItems(data)
}); });
})
.then(() => {
// re-fetch with expansions // re-fetch with expansions
const freshRow = await internalAccessList.get( return internalAccessList.get(access, {
access,
{
id: data.id, id: data.id,
expand: ["owner", "items", "clients", "proxy_hosts.[certificate,access_list.[clients,items]]"], expand: ['owner', 'items', 'clients', 'proxy_hosts.[certificate,access_list.[clients,items]]']
}, }, true /* <- skip masking */);
true // skip masking })
); .then((row) => {
return internalAccessList.build(row)
await internalAccessList.build(freshRow) .then(() => {
if (Number.parseInt(row.proxy_host_count, 10)) { if (parseInt(row.proxy_host_count, 10)) {
await internalNginx.bulkGenerateConfigs("proxy_host", row.proxy_hosts); return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
} }
await internalNginx.reload(); }).then(internalNginx.reload)
.then(() => {
return internalAccessList.maskItems(row); return internalAccessList.maskItems(row);
});
});
}, },
/** /**
@@ -208,50 +239,52 @@ const internalAccessList = {
* @param {Integer} data.id * @param {Integer} data.id
* @param {Array} [data.expand] * @param {Array} [data.expand]
* @param {Array} [data.omit] * @param {Array} [data.omit]
* @param {Boolean} [skipMasking] * @param {Boolean} [skip_masking]
* @return {Promise} * @return {Promise}
*/ */
get: async (access, data, skipMasking) => { get: (access, data, skip_masking) => {
const thisData = data || {}; if (typeof data === 'undefined') {
const accessData = await access.can("access_lists:get", thisData.id) data = {};
}
const query = accessListModel return access.can('access_lists:get', data.id)
.then((access_data) => {
let query = accessListModel
.query() .query()
.select("access_list.*", accessListModel.raw("COUNT(proxy_host.id) as proxy_host_count")) .select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count'))
.leftJoin("proxy_host", function () { .leftJoin('proxy_host', function() {
this.on("proxy_host.access_list_id", "=", "access_list.id").andOn( this.on('proxy_host.access_list_id', '=', 'access_list.id')
"proxy_host.is_deleted", .andOn('proxy_host.is_deleted', '=', 0);
"=",
0,
);
}) })
.where("access_list.is_deleted", 0) .where('access_list.is_deleted', 0)
.andWhere("access_list.id", thisData.id) .andWhere('access_list.id', data.id)
.groupBy("access_list.id") .groupBy('access_list.id')
.allowGraph("[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]") .allowGraph('[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]')
.first(); .first();
if (accessData.permission_visibility !== "all") { if (access_data.permission_visibility !== 'all') {
query.andWhere("access_list.owner_user_id", access.token.getUserId(1)); query.andWhere('access_list.owner_user_id', access.token.getUserId(1));
} }
if (typeof thisData.expand !== "undefined" && thisData.expand !== null) { if (typeof data.expand !== 'undefined' && data.expand !== null) {
query.withGraphFetched(`[${thisData.expand.join(", ")}]`); query.withGraphFetched('[' + data.expand.join(', ') + ']');
} }
let row = await query.then(utils.omitRow(omissions())); return query.then(utils.omitRow(omissions()));
})
.then((row) => {
if (!row || !row.id) { if (!row || !row.id) {
throw new errs.ItemNotFoundError(thisData.id); throw new error.ItemNotFoundError(data.id);
} }
if (!skipMasking && typeof row.items !== "undefined" && row.items) { if (!skip_masking && typeof row.items !== 'undefined' && row.items) {
row = internalAccessList.maskItems(row); row = internalAccessList.maskItems(row);
} }
// Custom omissions // Custom omissions
if (typeof data.omit !== "undefined" && data.omit !== null) { if (typeof data.omit !== 'undefined' && data.omit !== null) {
row = _.omit(row, data.omit); row = _.omit(row, data.omit);
} }
return row; return row;
});
}, },
/** /**
@@ -261,15 +294,14 @@ const internalAccessList = {
* @param {String} [data.reason] * @param {String} [data.reason]
* @returns {Promise} * @returns {Promise}
*/ */
delete: async (access, data) => { delete: (access, data) => {
await access.can("access_lists:delete", data.id); return access.can('access_lists:delete', data.id)
const row = await internalAccessList.get(access, { .then(() => {
id: data.id, return internalAccessList.get(access, {id: data.id, expand: ['proxy_hosts', 'items', 'clients']});
expand: ["proxy_hosts", "items", "clients"], })
}); .then((row) => {
if (!row || !row.id) { if (!row || !row.id) {
throw new errs.ItemNotFoundError(data.id); throw new error.ItemNotFoundError(data.id);
} }
// 1. update row to be deleted // 1. update row to be deleted
@@ -278,47 +310,57 @@ const internalAccessList = {
// 4. audit log // 4. audit log
// 1. update row to be deleted // 1. update row to be deleted
await accessListModel return accessListModel
.query() .query()
.where("id", row.id) .where('id', row.id)
.patch({ .patch({
is_deleted: 1, is_deleted: 1
}); })
.then(() => {
// 2. update any proxy hosts that were using it (ignoring permissions) // 2. update any proxy hosts that were using it (ignoring permissions)
if (row.proxy_hosts) { if (row.proxy_hosts) {
await proxyHostModel return proxyHostModel
.query() .query()
.where("access_list_id", "=", row.id) .where('access_list_id', '=', row.id)
.patch({ access_list_id: 0 }); .patch({access_list_id: 0})
.then(() => {
// 3. reconfigure those hosts, then reload nginx // 3. reconfigure those hosts, then reload nginx
// set the access_list_id to zero for these items // set the access_list_id to zero for these items
row.proxy_hosts.map((_val, idx) => { row.proxy_hosts.map(function (val, idx) {
row.proxy_hosts[idx].access_list_id = 0; row.proxy_hosts[idx].access_list_id = 0;
return true;
}); });
await internalNginx.bulkGenerateConfigs("proxy_host", row.proxy_hosts); return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
})
.then(() => {
return internalNginx.reload();
});
} }
})
await internalNginx.reload(); .then(() => {
// delete the htpasswd file // delete the htpasswd file
let htpasswd_file = internalAccessList.getFilename(row);
try { try {
fs.unlinkSync(internalAccessList.getFilename(row)); fs.unlinkSync(htpasswd_file);
} catch (_err) { } catch (err) {
// do nothing // do nothing
} }
})
.then(() => {
// 4. audit log // 4. audit log
await internalAuditLog.add(access, { return internalAuditLog.add(access, {
action: "deleted", action: 'deleted',
object_type: "access-list", object_type: 'access-list',
object_id: row.id, object_id: row.id,
meta: _.omit(internalAccessList.maskItems(row), ["is_deleted", "proxy_hosts"]), meta: _.omit(internalAccessList.maskItems(row), ['is_deleted', 'proxy_hosts'])
}); });
});
})
.then(() => {
return true; return true;
});
}, },
/** /**
@@ -326,73 +368,75 @@ const internalAccessList = {
* *
* @param {Access} access * @param {Access} access
* @param {Array} [expand] * @param {Array} [expand]
* @param {String} [searchQuery] * @param {String} [search_query]
* @returns {Promise} * @returns {Promise}
*/ */
getAll: async (access, expand, searchQuery) => { getAll: (access, expand, search_query) => {
const accessData = await access.can("access_lists:list"); return access.can('access_lists:list')
.then((access_data) => {
const query = accessListModel let query = accessListModel
.query() .query()
.select("access_list.*", accessListModel.raw("COUNT(proxy_host.id) as proxy_host_count")) .select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count'))
.leftJoin("proxy_host", function () { .leftJoin('proxy_host', function() {
this.on("proxy_host.access_list_id", "=", "access_list.id").andOn( this.on('proxy_host.access_list_id', '=', 'access_list.id')
"proxy_host.is_deleted", .andOn('proxy_host.is_deleted', '=', 0);
"=",
0,
);
}) })
.where("access_list.is_deleted", 0) .where('access_list.is_deleted', 0)
.groupBy("access_list.id") .groupBy('access_list.id')
.allowGraph("[owner,items,clients]") .allowGraph('[owner,items,clients]')
.orderBy("access_list.name", "ASC"); .orderBy('access_list.name', 'ASC');
if (accessData.permission_visibility !== "all") { if (access_data.permission_visibility !== 'all') {
query.andWhere("access_list.owner_user_id", access.token.getUserId(1)); query.andWhere('access_list.owner_user_id', access.token.getUserId(1));
} }
// Query is used for searching // Query is used for searching
if (typeof searchQuery === "string") { if (typeof search_query === 'string') {
query.where(function () { query.where(function () {
this.where("name", "like", `%${searchQuery}%`); this.where('name', 'like', '%' + search_query + '%');
}); });
} }
if (typeof expand !== "undefined" && expand !== null) { if (typeof expand !== 'undefined' && expand !== null) {
query.withGraphFetched(`[${expand.join(", ")}]`); query.withGraphFetched('[' + expand.join(', ') + ']');
} }
const rows = await query.then(utils.omitRows(omissions())); return query.then(utils.omitRows(omissions()));
})
.then((rows) => {
if (rows) { if (rows) {
rows.map((row, idx) => { rows.map(function (row, idx) {
if (typeof row.items !== "undefined" && row.items) { if (typeof row.items !== 'undefined' && row.items) {
rows[idx] = internalAccessList.maskItems(row); rows[idx] = internalAccessList.maskItems(row);
} }
return true;
}); });
} }
return rows; return rows;
});
}, },
/** /**
* Count is used in reports * Report use
* *
* @param {Integer} userId * @param {Integer} user_id
* @param {String} visibility * @param {String} visibility
* @returns {Promise} * @returns {Promise}
*/ */
getCount: async (userId, visibility) => { getCount: (user_id, visibility) => {
const query = accessListModel let query = accessListModel
.query() .query()
.count("id as count") .count('id as count')
.where("is_deleted", 0); .where('is_deleted', 0);
if (visibility !== "all") { if (visibility !== 'all') {
query.andWhere("owner_user_id", userId); query.andWhere('owner_user_id', user_id);
} }
const row = await query.first(); return query.first()
return Number.parseInt(row.count, 10); .then((row) => {
return parseInt(row.count, 10);
});
}, },
/** /**
@@ -400,21 +444,21 @@ const internalAccessList = {
* @returns {Object} * @returns {Object}
*/ */
maskItems: (list) => { maskItems: (list) => {
if (list && typeof list.items !== "undefined") { if (list && typeof list.items !== 'undefined') {
list.items.map((val, idx) => { list.items.map(function (val, idx) {
let repeatFor = 8; let repeat_for = 8;
let firstChar = "*"; let first_char = '*';
if (typeof val.password !== "undefined" && val.password) { if (typeof val.password !== 'undefined' && val.password) {
repeatFor = val.password.length - 1; repeat_for = val.password.length - 1;
firstChar = val.password.charAt(0); first_char = val.password.charAt(0);
} }
list.items[idx].hint = firstChar + "*".repeat(repeatFor); list.items[idx].hint = first_char + ('*').repeat(repeat_for);
list.items[idx].password = ""; list.items[idx].password = '';
return true;
}); });
} }
return list; return list;
}, },
@@ -424,7 +468,7 @@ const internalAccessList = {
* @returns {String} * @returns {String}
*/ */
getFilename: (list) => { getFilename: (list) => {
return `/data/access/${list.id}`; return '/data/access/' + list.id;
}, },
/** /**
@@ -434,33 +478,40 @@ const internalAccessList = {
* @param {Array} list.items * @param {Array} list.items
* @returns {Promise} * @returns {Promise}
*/ */
build: async (list) => { build: (list) => {
logger.info(`Building Access file #${list.id} for: ${list.name}`); logger.info('Building Access file #' + list.id + ' for: ' + list.name);
const htpasswdFile = internalAccessList.getFilename(list); return new Promise((resolve, reject) => {
let htpasswd_file = internalAccessList.getFilename(list);
// 1. remove any existing access file // 1. remove any existing access file
try { try {
fs.unlinkSync(htpasswdFile); fs.unlinkSync(htpasswd_file);
} catch (_err) { } catch (err) {
// do nothing // do nothing
} }
// 2. create empty access file // 2. create empty access file
fs.writeFileSync(htpasswdFile, '', {encoding: 'utf8'}); try {
fs.writeFileSync(htpasswd_file, '', {encoding: 'utf8'});
resolve(htpasswd_file);
} catch (err) {
reject(err);
}
})
.then((htpasswd_file) => {
// 3. generate password for each user // 3. generate password for each user
if (list.items.length) { if (list.items.length) {
await new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
batchflow(list.items).sequential() batchflow(list.items).sequential()
.each((_i, item, next) => { .each((i, item, next) => {
if (item.password?.length) { if (typeof item.password !== 'undefined' && item.password.length) {
logger.info(`Adding: ${item.username}`); logger.info('Adding: ' + item.username);
utils.execFile('openssl', ['passwd', '-apr1', item.password]) utils.execFile('openssl', ['passwd', '-apr1', item.password])
.then((res) => { .then((res) => {
try { try {
fs.appendFileSync(htpasswdFile, `${item.username}:${res}\n`, {encoding: 'utf8'}); fs.appendFileSync(htpasswd_file, item.username + ':' + res + '\n', {encoding: 'utf8'});
} catch (err) { } catch (err) {
reject(err); reject(err);
} }
@@ -477,12 +528,13 @@ const internalAccessList = {
reject(err); reject(err);
}) })
.end((results) => { .end((results) => {
logger.success(`Built Access file #${list.id} for: ${list.name}`); logger.success('Built Access file #' + list.id + ' for: ' + list.name);
resolve(results); resolve(results);
}); });
}); });
} }
});
} }
} };
export default internalAccessList; module.exports = internalAccessList;

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,76 +1,93 @@
import gravatar from "gravatar"; const _ = require('lodash');
import _ from "lodash"; const error = require('../lib/error');
import errs from "../lib/error.js"; const utils = require('../lib/utils');
import utils from "../lib/utils.js"; const userModel = require('../models/user');
import authModel from "../models/auth.js"; const userPermissionModel = require('../models/user_permission');
import userModel from "../models/user.js"; const authModel = require('../models/auth');
import userPermissionModel from "../models/user_permission.js"; const gravatar = require('gravatar');
import internalAuditLog from "./audit-log.js"; const internalToken = require('./token');
import internalToken from "./token.js"; const internalAuditLog = require('./audit-log');
const omissions = () => { function omissions () {
return ["is_deleted", "permissions.id", "permissions.user_id", "permissions.created_on", "permissions.modified_on"]; return ['is_deleted'];
}; }
const DEFAULT_AVATAR = gravatar.url("admin@example.com", { default: "mm" });
const internalUser = { const internalUser = {
/** /**
* Create a user can happen unauthenticated only once and only when no active users exist.
* Otherwise, a valid auth method is required.
*
* @param {Access} access * @param {Access} access
* @param {Object} data * @param {Object} data
* @returns {Promise} * @returns {Promise}
*/ */
create: async (access, data) => { create: (access, data) => {
const auth = data.auth || null; let auth = data.auth || null;
delete data.auth; delete data.auth;
data.avatar = data.avatar || ""; data.avatar = data.avatar || '';
data.roles = data.roles || []; data.roles = data.roles || [];
if (typeof data.is_disabled !== "undefined") { if (typeof data.is_disabled !== 'undefined') {
data.is_disabled = data.is_disabled ? 1 : 0; data.is_disabled = data.is_disabled ? 1 : 0;
} }
await access.can("users:create", data); return access.can('users:create', data)
data.avatar = gravatar.url(data.email, { default: "mm" }); .then(() => {
data.avatar = gravatar.url(data.email, {default: 'mm'});
let user = await userModel.query().insertAndFetch(data).then(utils.omitRow(omissions())); return userModel
.query()
.insertAndFetch(data)
.then(utils.omitRow(omissions()));
})
.then((user) => {
if (auth) { if (auth) {
user = await authModel.query().insert({ return authModel
.query()
.insert({
user_id: user.id, user_id: user.id,
type: auth.type, type: auth.type,
secret: auth.secret, secret: auth.secret,
meta: {}, meta: {}
}); })
} .then(() => {
// Create permissions row as well
const isAdmin = data.roles.indexOf("admin") !== -1;
await userPermissionModel.query().insert({
user_id: user.id,
visibility: isAdmin ? "all" : "user",
proxy_hosts: "manage",
redirection_hosts: "manage",
dead_hosts: "manage",
streams: "manage",
access_lists: "manage",
certificates: "manage",
});
user = await internalUser.get(access, { id: user.id, expand: ["permissions"] });
await internalAuditLog.add(access, {
action: "created",
object_type: "user",
object_id: user.id,
meta: user,
});
return user; return user;
});
} else {
return user;
}
})
.then((user) => {
// Create permissions row as well
let is_admin = data.roles.indexOf('admin') !== -1;
return userPermissionModel
.query()
.insert({
user_id: user.id,
visibility: is_admin ? 'all' : 'user',
proxy_hosts: 'manage',
redirection_hosts: 'manage',
dead_hosts: 'manage',
streams: 'manage',
access_lists: 'manage',
certificates: 'manage'
})
.then(() => {
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
})
.then(() => {
return user;
});
});
}, },
/** /**
@@ -82,25 +99,29 @@ const internalUser = {
* @return {Promise} * @return {Promise}
*/ */
update: (access, data) => { update: (access, data) => {
if (typeof data.is_disabled !== "undefined") { if (typeof data.is_disabled !== 'undefined') {
data.is_disabled = data.is_disabled ? 1 : 0; data.is_disabled = data.is_disabled ? 1 : 0;
} }
return access return access.can('users:update', data.id)
.can("users:update", data.id)
.then(() => { .then(() => {
// Make sure that the user being updated doesn't change their email to another user that is already using it // 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 // 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 // 2. if email is to be changed, find other users with that email
if (typeof data.email !== "undefined") { if (typeof data.email !== 'undefined') {
data.email = data.email.toLowerCase().trim(); data.email = data.email.toLowerCase().trim();
if (user.email !== data.email) { if (user.email !== data.email) {
return internalUser.isEmailAvailable(data.email, data.id).then((available) => { return internalUser.isEmailAvailable(data.email, data.id)
.then((available) => {
if (!available) { if (!available) {
throw new errs.ValidationError(`Email address already in use - ${data.email}`); throw new error.ValidationError('Email address already in use - ' + data.email);
} }
return user; return user;
}); });
} }
@@ -113,25 +134,26 @@ const internalUser = {
.then((user) => { .then((user) => {
if (user.id !== data.id) { if (user.id !== data.id) {
// Sanity check that something crazy hasn't happened // Sanity check that something crazy hasn't happened
throw new errs.InternalValidationError( throw new error.InternalValidationError('User could not be updated, IDs do not match: ' + user.id + ' !== ' + data.id);
`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(() => { .then(() => {
return internalUser.get(access, {id: data.id}); return internalUser.get(access, {id: data.id});
}) })
.then((user) => { .then((user) => {
// Add to audit log // Add to audit log
return internalAuditLog return internalAuditLog.add(access, {
.add(access, { action: 'updated',
action: "updated", object_type: 'user',
object_type: "user",
object_id: user.id, object_id: user.id,
meta: data, meta: data
}) })
.then(() => { .then(() => {
return user; return user;
@@ -148,41 +170,37 @@ const internalUser = {
* @return {Promise} * @return {Promise}
*/ */
get: (access, data) => { get: (access, data) => {
const thisData = data || {}; if (typeof data === 'undefined') {
data = {};
if (typeof thisData.id === "undefined" || !thisData.id) {
thisData.id = access.token.getUserId(0);
} }
return access if (typeof data.id === 'undefined' || !data.id) {
.can("users:get", thisData.id) data.id = access.token.getUserId(0);
}
return access.can('users:get', data.id)
.then(() => { .then(() => {
const query = userModel let query = userModel
.query() .query()
.where("is_deleted", 0) .where('is_deleted', 0)
.andWhere("id", thisData.id) .andWhere('id', data.id)
.allowGraph("[permissions]") .allowGraph('[permissions]')
.first(); .first();
if (typeof thisData.expand !== "undefined" && thisData.expand !== null) { if (typeof data.expand !== 'undefined' && data.expand !== null) {
query.withGraphFetched(`[${thisData.expand.join(", ")}]`); query.withGraphFetched('[' + data.expand.join(', ') + ']');
} }
return query.then(utils.omitRow(omissions())); return query.then(utils.omitRow(omissions()));
}) })
.then((row) => { .then((row) => {
if (!row || !row.id) { if (!row || !row.id) {
throw new errs.ItemNotFoundError(thisData.id); throw new error.ItemNotFoundError(data.id);
} }
// Custom omissions // Custom omissions
if (typeof thisData.omit !== "undefined" && thisData.omit !== null) { if (typeof data.omit !== 'undefined' && data.omit !== null) {
return _.omit(row, thisData.omit); row = _.omit(row, data.omit);
} }
if (row.avatar === "") {
row.avatar = DEFAULT_AVATAR;
}
return row; return row;
}); });
}, },
@@ -195,13 +213,18 @@ const internalUser = {
* @param user_id * @param user_id
*/ */
isEmailAvailable: (email, user_id) => { isEmailAvailable: (email, user_id) => {
const query = userModel.query().where("email", "=", email.toLowerCase().trim()).where("is_deleted", 0).first(); let query = userModel
.query()
.where('email', '=', email.toLowerCase().trim())
.where('is_deleted', 0)
.first();
if (typeof user_id !== "undefined") { if (typeof user_id !== 'undefined') {
query.where("id", "!=", user_id); query.where('id', '!=', user_id);
} }
return query.then((user) => { return query
.then((user) => {
return !user; return !user;
}); });
}, },
@@ -214,34 +237,33 @@ const internalUser = {
* @returns {Promise} * @returns {Promise}
*/ */
delete: (access, data) => { delete: (access, data) => {
return access return access.can('users:delete', data.id)
.can("users:delete", data.id)
.then(() => { .then(() => {
return internalUser.get(access, {id: data.id}); return internalUser.get(access, {id: data.id});
}) })
.then((user) => { .then((user) => {
if (!user) { if (!user) {
throw new errs.ItemNotFoundError(data.id); throw new error.ItemNotFoundError(data.id);
} }
// Make sure user can't delete themselves // Make sure user can't delete themselves
if (user.id === access.token.getUserId(0)) { if (user.id === access.token.getUserId(0)) {
throw new errs.PermissionError("You cannot delete yourself."); throw new error.PermissionError('You cannot delete yourself.');
} }
return userModel return userModel
.query() .query()
.where("id", user.id) .where('id', user.id)
.patch({ .patch({
is_deleted: 1, is_deleted: 1
}) })
.then(() => { .then(() => {
// Add to audit log // Add to audit log
return internalAuditLog.add(access, { return internalAuditLog.add(access, {
action: "deleted", action: 'deleted',
object_type: "user", object_type: 'user',
object_id: user.id, object_id: user.id,
meta: _.omit(user, omissions()), meta: _.omit(user, omissions())
}); });
}); });
}) })
@@ -250,14 +272,6 @@ const internalUser = {
}); });
}, },
deleteAll: async () => {
await userModel
.query()
.patch({
is_deleted: 1,
});
},
/** /**
* This will only count the users * This will only count the users
* *
@@ -266,26 +280,26 @@ const internalUser = {
* @returns {*} * @returns {*}
*/ */
getCount: (access, search_query) => { getCount: (access, search_query) => {
return access return access.can('users:list')
.can("users:list")
.then(() => { .then(() => {
const query = userModel.query().count("id as count").where("is_deleted", 0).first(); let query = userModel
.query()
.count('id as count')
.where('is_deleted', 0)
.first();
// Query is used for searching // Query is used for searching
if (typeof search_query === "string") { if (typeof search_query === 'string') {
query.where(function () { query.where(function () {
this.where("user.name", "like", `%${search_query}%`).orWhere( this.where('user.name', 'like', '%' + search_query + '%')
"user.email", .orWhere('user.email', 'like', '%' + search_query + '%');
"like",
`%${search_query}%`,
);
}); });
} }
return query; return query;
}) })
.then((row) => { .then((row) => {
return Number.parseInt(row.count, 10); return parseInt(row.count, 10);
}); });
}, },
@@ -297,28 +311,30 @@ const internalUser = {
* @param {String} [search_query] * @param {String} [search_query]
* @returns {Promise} * @returns {Promise}
*/ */
getAll: async (access, expand, search_query) => { getAll: (access, expand, search_query) => {
await access.can("users:list"); return access.can('users:list')
const query = userModel .then(() => {
let query = userModel
.query() .query()
.where("is_deleted", 0) .where('is_deleted', 0)
.groupBy("id") .groupBy('id')
.allowGraph("[permissions]") .allowGraph('[permissions]')
.orderBy("name", "ASC"); .orderBy('name', 'ASC');
// Query is used for searching // Query is used for searching
if (typeof search_query === "string") { if (typeof search_query === 'string') {
query.where(function () { query.where(function () {
this.where("name", "like", `%${search_query}%`).orWhere("email", "like", `%${search_query}%`); this.where('name', 'like', '%' + search_query + '%')
.orWhere('email', 'like', '%' + search_query + '%');
}); });
} }
if (typeof expand !== "undefined" && expand !== null) { if (typeof expand !== 'undefined' && expand !== null) {
query.withGraphFetched(`[${expand.join(", ")}]`); query.withGraphFetched('[' + expand.join(', ') + ']');
} }
const res = await query; return query.then(utils.omitRows(omissions()));
return utils.omitRows(omissions())(res); });
}, },
/** /**
@@ -326,11 +342,11 @@ const internalUser = {
* @param {Integer} [id_requested] * @param {Integer} [id_requested]
* @returns {[String]} * @returns {[String]}
*/ */
getUserOmisionsByAccess: (access, idRequested) => { getUserOmisionsByAccess: (access, id_requested) => {
let response = []; // Admin response let response = []; // Admin response
if (!access.token.hasScope("admin") && access.token.getUserId(0) !== idRequested) { if (!access.token.hasScope('admin') && access.token.getUserId(0) !== id_requested) {
response = ["is_deleted"]; // Restricted response response = ['roles', 'is_deleted']; // Restricted response
} }
return response; return response;
@@ -345,29 +361,25 @@ const internalUser = {
* @return {Promise} * @return {Promise}
*/ */
setPassword: (access, data) => { setPassword: (access, data) => {
return access return access.can('users:password', data.id)
.can("users:password", data.id)
.then(() => { .then(() => {
return internalUser.get(access, {id: data.id}); return internalUser.get(access, {id: data.id});
}) })
.then((user) => { .then((user) => {
if (user.id !== data.id) { if (user.id !== data.id) {
// Sanity check that something crazy hasn't happened // Sanity check that something crazy hasn't happened
throw new errs.InternalValidationError( throw new error.InternalValidationError('User could not be updated, IDs do not match: ' + user.id + ' !== ' + data.id);
`User could not be updated, IDs do not match: ${user.id} !== ${data.id}`,
);
} }
if (user.id === access.token.getUserId(0)) { if (user.id === access.token.getUserId(0)) {
// they're setting their own password. Make sure their current password is correct // they're setting their own password. Make sure their current password is correct
if (typeof data.current === "undefined" || !data.current) { if (typeof data.current === 'undefined' || !data.current) {
throw new errs.ValidationError("Current password was not supplied"); throw new error.ValidationError('Current password was not supplied');
} }
return internalToken return internalToken.getTokenFromEmail({
.getTokenFromEmail({
identity: user.email, identity: user.email,
secret: data.current, secret: data.current
}) })
.then(() => { .then(() => {
return user; return user;
@@ -380,36 +392,43 @@ const internalUser = {
// Get auth, patch if it exists // Get auth, patch if it exists
return authModel return authModel
.query() .query()
.where("user_id", user.id) .where('user_id', user.id)
.andWhere("type", data.type) .andWhere('type', data.type)
.first() .first()
.then((existing_auth) => { .then((existing_auth) => {
if (existing_auth) { if (existing_auth) {
// patch // patch
return authModel.query().where("user_id", user.id).andWhere("type", data.type).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 type: data.type, // This is required for the model to encrypt on save
secret: data.secret, secret: data.secret
}); });
} } else {
// insert // insert
return authModel.query().insert({ return authModel
.query()
.insert({
user_id: user.id, user_id: user.id,
type: data.type, type: data.type,
secret: data.secret, secret: data.secret,
meta: {}, meta: {}
}); });
}
}) })
.then(() => { .then(() => {
// Add to Audit Log // Add to Audit Log
return internalAuditLog.add(access, { return internalAuditLog.add(access, {
action: "updated", action: 'updated',
object_type: "user", object_type: 'user',
object_id: user.id, object_id: user.id,
meta: { meta: {
name: user.name, name: user.name,
password_changed: true, password_changed: true,
auth_type: data.type, auth_type: data.type
}, }
}); });
}); });
}) })
@@ -424,17 +443,14 @@ const internalUser = {
* @return {Promise} * @return {Promise}
*/ */
setPermissions: (access, data) => { setPermissions: (access, data) => {
return access return access.can('users:permissions', data.id)
.can("users:permissions", data.id)
.then(() => { .then(() => {
return internalUser.get(access, {id: data.id}); return internalUser.get(access, {id: data.id});
}) })
.then((user) => { .then((user) => {
if (user.id !== data.id) { if (user.id !== data.id) {
// Sanity check that something crazy hasn't happened // Sanity check that something crazy hasn't happened
throw new errs.InternalValidationError( throw new error.InternalValidationError('User could not be updated, IDs do not match: ' + user.id + ' !== ' + data.id);
`User could not be updated, IDs do not match: ${user.id} !== ${data.id}`,
);
} }
return user; return user;
@@ -443,30 +459,34 @@ const internalUser = {
// Get perms row, patch if it exists // Get perms row, patch if it exists
return userPermissionModel return userPermissionModel
.query() .query()
.where("user_id", user.id) .where('user_id', user.id)
.first() .first()
.then((existing_auth) => { .then((existing_auth) => {
if (existing_auth) { if (existing_auth) {
// patch // patch
return userPermissionModel return userPermissionModel
.query() .query()
.where("user_id", user.id) .where('user_id', user.id)
.patchAndFetchById(existing_auth.id, _.assign({user_id: user.id}, data)); .patchAndFetchById(existing_auth.id, _.assign({user_id: user.id}, data));
} } else {
// insert // insert
return userPermissionModel.query().insertAndFetch(_.assign({ user_id: user.id }, data)); return userPermissionModel
.query()
.insertAndFetch(_.assign({user_id: user.id}, data));
}
}) })
.then((permissions) => { .then((permissions) => {
// Add to Audit Log // Add to Audit Log
return internalAuditLog.add(access, { return internalAuditLog.add(access, {
action: "updated", action: 'updated',
object_type: "user", object_type: 'user',
object_id: user.id, object_id: user.id,
meta: { meta: {
name: user.name, name: user.name,
permissions: permissions, permissions: permissions
}, }
}); });
}); });
}) })
.then(() => { .then(() => {
@@ -480,15 +500,14 @@ const internalUser = {
* @param {Integer} data.id * @param {Integer} data.id
*/ */
loginAs: (access, data) => { loginAs: (access, data) => {
return access return access.can('users:loginas', data.id)
.can("users:loginas", data.id)
.then(() => { .then(() => {
return internalUser.get(access, data); return internalUser.get(access, data);
}) })
.then((user) => { .then((user) => {
return internalToken.getTokenFromUser(user); return internalToken.getTokenFromUser(user);
}); });
}, }
}; };
export default internalUser; module.exports = internalUser;

View File

@@ -4,31 +4,27 @@
* "scope" in this file means "where did this token come from and what is using it", so 99% of the time * "scope" in this file means "where did this token come from and what is using it", so 99% of the time
* the "scope" is going to be "user" because it would be a user token. This is not to be confused with * the "scope" is going to be "user" because it would be a user token. This is not to be confused with
* the "role" which could be "user" or "admin". The scope in fact, could be "worker" or anything else. * the "role" which could be "user" or "admin". The scope in fact, could be "worker" or anything else.
*
*
*/ */
import fs from "node:fs"; const _ = require('lodash');
import { dirname } from "node:path"; const logger = require('../logger').access;
import { fileURLToPath } from "node:url"; const Ajv = require('ajv/dist/2020');
import Ajv from "ajv/dist/2020.js"; const error = require('./error');
import _ from "lodash"; const userModel = require('../models/user');
import { access as logger } from "../logger.js"; const proxyHostModel = require('../models/proxy_host');
import proxyHostModel from "../models/proxy_host.js"; const TokenModel = require('../models/token');
import TokenModel from "../models/token.js"; const roleSchema = require('./access/roles.json');
import userModel from "../models/user.js"; const permsSchema = require('./access/permissions.json');
import permsSchema from "./access/permissions.json" with { type: "json" };
import roleSchema from "./access/roles.json" with { type: "json" };
import errs from "./error.js";
const __filename = fileURLToPath(import.meta.url); module.exports = function (token_string) {
const __dirname = dirname(__filename); let Token = new TokenModel();
let token_data = null;
export default function (tokenString) {
const Token = TokenModel();
let tokenData = null;
let initialised = false; let initialised = false;
const objectCache = {}; let object_cache = {};
let allowInternalAccess = false; let allow_internal_access = false;
let userRoles = []; let user_roles = [];
let permissions = {}; let permissions = {};
/** /**
@@ -36,58 +32,63 @@ export default function (tokenString) {
* *
* @returns {Promise} * @returns {Promise}
*/ */
this.init = async () => { this.init = () => {
return new Promise((resolve, reject) => {
if (initialised) { if (initialised) {
return; resolve();
} } else if (!token_string) {
reject(new error.PermissionError('Permission Denied'));
if (!tokenString) { } else {
throw new errs.PermissionError("Permission Denied"); resolve(Token.load(token_string)
} .then((data) => {
token_data = data;
tokenData = await Token.load(tokenString);
// At this point we need to load the user from the DB and make sure they: // At this point we need to load the user from the DB and make sure they:
// - exist (and not soft deleted) // - exist (and not soft deleted)
// - still have the appropriate scopes for this token // - still have the appropriate scopes for this token
// This is only required when the User ID is supplied or if the token scope has `user` // This is only required when the User ID is supplied or if the token scope has `user`
if (
tokenData.attrs.id ||
(typeof tokenData.scope !== "undefined" && _.indexOf(tokenData.scope, "user") !== -1)
) {
// Has token user id or token user scope
const user = await userModel
.query()
.where("id", tokenData.attrs.id)
.andWhere("is_deleted", 0)
.andWhere("is_disabled", 0)
.allowGraph("[permissions]")
.withGraphFetched("[permissions]")
.first();
if (token_data.attrs.id || (typeof token_data.scope !== 'undefined' && _.indexOf(token_data.scope, 'user') !== -1)) {
// Has token user id or token user scope
return userModel
.query()
.where('id', token_data.attrs.id)
.andWhere('is_deleted', 0)
.andWhere('is_disabled', 0)
.allowGraph('[permissions]')
.withGraphFetched('[permissions]')
.first()
.then((user) => {
if (user) { if (user) {
// make sure user has all scopes of the token // make sure user has all scopes of the token
// The `user` role is not added against the user row, so we have to just add it here to get past this check. // The `user` role is not added against the user row, so we have to just add it here to get past this check.
user.roles.push("user"); user.roles.push('user');
let ok = true; let is_ok = true;
_.forEach(tokenData.scope, (scope_item) => { _.forEach(token_data.scope, (scope_item) => {
if (_.indexOf(user.roles, scope_item) === -1) { if (_.indexOf(user.roles, scope_item) === -1) {
ok = false; is_ok = false;
} }
}); });
if (!ok) { if (!is_ok) {
throw new errs.AuthError("Invalid token scope for User"); throw new error.AuthError('Invalid token scope for User');
}
initialised = true;
userRoles = user.roles;
permissions = user.permissions;
} else { } else {
throw new errs.AuthError("User cannot be loaded for Token");
}
}
initialised = true; initialised = true;
user_roles = user.roles;
permissions = user.permissions;
}
} else {
throw new error.AuthError('User cannot be loaded for Token');
}
});
} else {
initialised = true;
}
}));
}
});
}; };
/** /**
@@ -95,121 +96,140 @@ export default function (tokenString) {
* This only applies to USER token scopes, as all other tokens are not really bound * This only applies to USER token scopes, as all other tokens are not really bound
* by object scopes * by object scopes
* *
* @param {String} objectType * @param {String} object_type
* @returns {Promise} * @returns {Promise}
*/ */
this.loadObjects = async (objectType) => { this.loadObjects = (object_type) => {
let objects = null; return new Promise((resolve, reject) => {
if (Token.hasScope('user')) {
if (Token.hasScope("user")) { if (typeof token_data.attrs.id === 'undefined' || !token_data.attrs.id) {
if (typeof tokenData.attrs.id === "undefined" || !tokenData.attrs.id) { reject(new error.AuthError('User Token supplied without a User ID'));
throw new errs.AuthError("User Token supplied without a User ID");
}
const tokenUserId = tokenData.attrs.id ? tokenData.attrs.id : 0;
if (typeof objectCache[objectType] !== "undefined") {
objects = objectCache[objectType];
} else { } else {
switch (objectType) { let token_user_id = token_data.attrs.id ? token_data.attrs.id : 0;
let query;
if (typeof object_cache[object_type] === 'undefined') {
switch (object_type) {
// USERS - should only return yourself // USERS - should only return yourself
case "users": case 'users':
objects = tokenUserId ? [tokenUserId] : []; resolve(token_user_id ? [token_user_id] : []);
break; break;
// Proxy Hosts // Proxy Hosts
case "proxy_hosts": { case 'proxy_hosts':
const query = proxyHostModel query = proxyHostModel
.query() .query()
.select("id") .select('id')
.andWhere("is_deleted", 0); .andWhere('is_deleted', 0);
if (permissions.visibility === "user") { if (permissions.visibility === 'user') {
query.andWhere("owner_user_id", tokenUserId); query.andWhere('owner_user_id', token_user_id);
} }
const rows = await query; resolve(query
objects = []; .then((rows) => {
_.forEach(rows, (ruleRow) => { let result = [];
result.push(ruleRow.id); _.forEach(rows, (rule_row) => {
result.push(rule_row.id);
}); });
// enum should not have less than 1 item // enum should not have less than 1 item
if (!objects.length) { if (!result.length) {
objects.push(0); result.push(0);
} }
return result;
})
);
break;
// DEFAULT: null
default:
resolve(null);
break; break;
} }
} } else {
objectCache[objectType] = objects; resolve(object_cache[object_type]);
} }
} }
} else {
resolve(null);
}
})
.then((objects) => {
object_cache[object_type] = objects;
return objects; return objects;
});
}; };
/** /**
* Creates a schema object on the fly with the IDs and other values required to be checked against the permissionSchema * Creates a schema object on the fly with the IDs and other values required to be checked against the permissionSchema
* *
* @param {String} permissionLabel * @param {String} permission_label
* @returns {Object} * @returns {Object}
*/ */
this.getObjectSchema = async (permissionLabel) => { this.getObjectSchema = (permission_label) => {
const baseObjectType = permissionLabel.split(":").shift(); let base_object_type = permission_label.split(':').shift();
const schema = { let schema = {
$id: "objects", $id: 'objects',
description: "Actor Properties", description: 'Actor Properties',
type: "object", type: 'object',
additionalProperties: false, additionalProperties: false,
properties: { properties: {
user_id: { user_id: {
anyOf: [ anyOf: [
{ {
type: "number", type: 'number',
enum: [Token.get("attrs").id], enum: [Token.get('attrs').id]
}, }
], ]
}, },
scope: { scope: {
type: "string", type: 'string',
pattern: `^${Token.get("scope")}$`, pattern: '^' + Token.get('scope') + '$'
}, }
}, }
}; };
const result = await this.loadObjects(baseObjectType); return this.loadObjects(base_object_type)
if (typeof result === "object" && result !== null) { .then((object_result) => {
schema.properties[baseObjectType] = { if (typeof object_result === 'object' && object_result !== null) {
type: "number", schema.properties[base_object_type] = {
enum: result, type: 'number',
minimum: 1, enum: object_result,
minimum: 1
}; };
} else { } else {
schema.properties[baseObjectType] = { schema.properties[base_object_type] = {
type: "number", type: 'number',
minimum: 1, minimum: 1
}; };
} }
return schema; return schema;
});
}; };
// here:
return { return {
token: Token, token: Token,
/** /**
* *
* @param {Boolean} [allowInternal] * @param {Boolean} [allow_internal]
* @returns {Promise} * @returns {Promise}
*/ */
load: async (allowInternal) => { load: (allow_internal) => {
if (tokenString) { return new Promise(function (resolve/*, reject*/) {
return await Token.load(tokenString); if (token_string) {
resolve(Token.load(token_string));
} else {
allow_internal_access = allow_internal;
resolve(allow_internal_access || null);
} }
allowInternalAccess = allowInternal; });
return allowInternal || null;
}, },
reloadObjects: this.loadObjects, reloadObjects: this.loadObjects,
@@ -220,59 +240,68 @@ export default function (tokenString) {
* @param {*} [data] * @param {*} [data]
* @returns {Promise} * @returns {Promise}
*/ */
can: async (permission, data) => { can: (permission, data) => {
if (allowInternalAccess === true) { if (allow_internal_access === true) {
return true; return Promise.resolve(true);
} //return true;
} else {
try { return this.init()
await this.init(); .then(() => {
const objectSchema = await this.getObjectSchema(permission); // Initialised, token decoded ok
return this.getObjectSchema(permission)
const dataSchema = { .then((objectSchema) => {
const data_schema = {
[permission]: { [permission]: {
data: data, data: data,
scope: Token.get("scope"), scope: Token.get('scope'),
roles: userRoles, roles: user_roles,
permission_visibility: permissions.visibility, permission_visibility: permissions.visibility,
permission_proxy_hosts: permissions.proxy_hosts, permission_proxy_hosts: permissions.proxy_hosts,
permission_redirection_hosts: permissions.redirection_hosts, permission_redirection_hosts: permissions.redirection_hosts,
permission_dead_hosts: permissions.dead_hosts, permission_dead_hosts: permissions.dead_hosts,
permission_streams: permissions.streams, permission_streams: permissions.streams,
permission_access_lists: permissions.access_lists, permission_access_lists: permissions.access_lists,
permission_certificates: permissions.certificates, permission_certificates: permissions.certificates
}, }
}; };
const permissionSchema = { let permissionSchema = {
$async: true, $async: true,
$id: "permissions", $id: 'permissions',
type: "object", type: 'object',
additionalProperties: false, additionalProperties: false,
properties: {}, properties: {}
}; };
const rawData = fs.readFileSync(`${__dirname}/access/${permission.replace(/:/gim, "-")}.json`, { permissionSchema.properties[permission] = require('./access/' + permission.replace(/:/gim, '-') + '.json');
encoding: "utf8",
});
permissionSchema.properties[permission] = JSON.parse(rawData);
const ajv = new Ajv({ const ajv = new Ajv({
verbose: true, verbose: true,
allErrors: true, allErrors: true,
breakOnError: true, breakOnError: true,
coerceTypes: true, coerceTypes: true,
schemas: [roleSchema, permsSchema, objectSchema, permissionSchema], schemas: [
roleSchema,
permsSchema,
objectSchema,
permissionSchema
]
}); });
const valid = ajv.validate("permissions", dataSchema); return ajv.validate('permissions', data_schema)
return valid && dataSchema[permission]; .then(() => {
} catch (err) { return data_schema[permission];
});
});
})
.catch((err) => {
err.permission = permission; err.permission = permission;
err.permission_data = data; err.permission_data = data;
logger.error(permission, data, err.message); logger.error(permission, data, err.message);
throw errs.PermissionError("Permission Denied", err);
throw new error.PermissionError('Permission Denied', err);
});
}
} }
},
}; };
} };

View File

@@ -1,15 +1,17 @@
import batchflow from "batchflow"; const dnsPlugins = require('../global/certbot-dns-plugins.json');
import dnsPlugins from "../global/certbot-dns-plugins.json" with { type: "json" }; const utils = require('./utils');
import { certbot as logger } from "../logger.js"; const error = require('./error');
import errs from "./error.js"; const logger = require('../logger').certbot;
import utils from "./utils.js"; const batchflow = require('batchflow');
const CERTBOT_VERSION_REPLACEMENT = "$(certbot --version | grep -Eo '[0-9](\\.[0-9]+)+')"; const CERTBOT_VERSION_REPLACEMENT = '$(certbot --version | grep -Eo \'[0-9](\\.[0-9]+)+\')';
const certbot = {
/** /**
* @param {array} pluginKeys * @param {array} pluginKeys
*/ */
const installPlugins = async (pluginKeys) => { installPlugins: async (pluginKeys) => {
let hasErrors = false; let hasErrors = false;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@@ -18,11 +20,9 @@ const installPlugins = async (pluginKeys) => {
return; return;
} }
batchflow(pluginKeys) batchflow(pluginKeys).sequential()
.sequential()
.each((_i, pluginKey, next) => { .each((_i, pluginKey, next) => {
certbot certbot.installPlugin(pluginKey)
.installPlugin(pluginKey)
.then(() => { .then(() => {
next(); next();
}) })
@@ -36,15 +36,13 @@ const installPlugins = async (pluginKeys) => {
}) })
.end(() => { .end(() => {
if (hasErrors) { if (hasErrors) {
reject( reject(new error.CommandError('Some plugins failed to install. Please check the logs above', 1));
new errs.CommandError("Some plugins failed to install. Please check the logs above", 1),
);
} else { } else {
resolve(); resolve();
} }
}); });
}); });
}; },
/** /**
* Installs a cerbot plugin given the key for the object from * Installs a cerbot plugin given the key for the object from
@@ -53,10 +51,10 @@ const installPlugins = async (pluginKeys) => {
* @param {string} pluginKey * @param {string} pluginKey
* @returns {Object} * @returns {Object}
*/ */
const installPlugin = async (pluginKey) => { installPlugin: async (pluginKey) => {
if (typeof dnsPlugins[pluginKey] === "undefined") { if (typeof dnsPlugins[pluginKey] === 'undefined') {
// throw Error(`Certbot plugin ${pluginKey} not found`); // throw Error(`Certbot plugin ${pluginKey} not found`);
throw new errs.ItemNotFoundError(pluginKey); throw new error.ItemNotFoundError(pluginKey);
} }
const plugin = dnsPlugins[pluginKey]; const plugin = dnsPlugins[pluginKey];
@@ -67,14 +65,13 @@ const installPlugin = async (pluginKey) => {
// SETUPTOOLS_USE_DISTUTILS is required for certbot plugins to install correctly // SETUPTOOLS_USE_DISTUTILS is required for certbot plugins to install correctly
// in new versions of Python // in new versions of Python
let env = Object.assign({}, process.env, { SETUPTOOLS_USE_DISTUTILS: "stdlib" }); let env = Object.assign({}, process.env, {SETUPTOOLS_USE_DISTUTILS: 'stdlib'});
if (typeof plugin.env === "object") { if (typeof plugin.env === 'object') {
env = Object.assign(env, plugin.env); env = Object.assign(env, plugin.env);
} }
const cmd = `. /opt/certbot/bin/activate && pip install --no-cache-dir ${plugin.dependencies} ${plugin.package_name}${plugin.version} && deactivate`; const cmd = `. /opt/certbot/bin/activate && pip install --no-cache-dir ${plugin.dependencies} ${plugin.package_name}${plugin.version} && deactivate`;
return utils return utils.exec(cmd, {env})
.exec(cmd, { env })
.then((result) => { .then((result) => {
logger.complete(`Installed ${pluginKey}`); logger.complete(`Installed ${pluginKey}`);
return result; return result;
@@ -82,6 +79,7 @@ const installPlugin = async (pluginKey) => {
.catch((err) => { .catch((err) => {
throw err; throw err;
}); });
},
}; };
export { installPlugins, installPlugin }; module.exports = certbot;

View File

@@ -1,6 +1,6 @@
import fs from "node:fs"; const fs = require('fs');
import NodeRSA from "node-rsa"; const NodeRSA = require('node-rsa');
import { global as logger } from "../logger.js"; const logger = require('../logger').global;
const keysFile = '/data/keys.json'; const keysFile = '/data/keys.json';
const mysqlEngine = 'mysql2'; const mysqlEngine = 'mysql2';
@@ -12,18 +12,16 @@ let instance = null;
// 1. Load from config file first (not recommended anymore) // 1. Load from config file first (not recommended anymore)
// 2. Use config env variables next // 2. Use config env variables next
const configure = () => { const configure = () => {
const filename = `${process.env.NODE_CONFIG_DIR || "./config"}/${process.env.NODE_ENV || "default"}.json`; const filename = (process.env.NODE_CONFIG_DIR || './config') + '/' + (process.env.NODE_ENV || 'default') + '.json';
if (fs.existsSync(filename)) { if (fs.existsSync(filename)) {
let configData; let configData;
try { try {
// Load this json synchronously configData = require(filename);
const rawData = fs.readFileSync(filename);
configData = JSON.parse(rawData);
} catch (_) { } catch (_) {
// do nothing // do nothing
} }
if (configData?.database) { if (configData && configData.database) {
logger.info(`Using configuration from file: ${filename}`); logger.info(`Using configuration from file: ${filename}`);
instance = configData; instance = configData;
instance.keys = getKeys(); instance.keys = getKeys();
@@ -36,7 +34,7 @@ const configure = () => {
const envMysqlName = process.env.DB_MYSQL_NAME || null; const envMysqlName = process.env.DB_MYSQL_NAME || null;
if (envMysqlHost && envMysqlUser && envMysqlName) { if (envMysqlHost && envMysqlUser && envMysqlName) {
// we have enough mysql creds to go with mysql // we have enough mysql creds to go with mysql
logger.info("Using MySQL configuration"); logger.info('Using MySQL configuration');
instance = { instance = {
database: { database: {
engine: mysqlEngine, engine: mysqlEngine,
@@ -56,7 +54,7 @@ const configure = () => {
const envPostgresName = process.env.DB_POSTGRES_NAME || null; const envPostgresName = process.env.DB_POSTGRES_NAME || null;
if (envPostgresHost && envPostgresUser && envPostgresName) { if (envPostgresHost && envPostgresUser && envPostgresName) {
// we have enough postgres creds to go with postgres // we have enough postgres creds to go with postgres
logger.info("Using Postgres configuration"); logger.info('Using Postgres configuration');
instance = { instance = {
database: { database: {
engine: postgresEngine, engine: postgresEngine,
@@ -71,18 +69,18 @@ const configure = () => {
return; return;
} }
const envSqliteFile = process.env.DB_SQLITE_FILE || "/data/database.sqlite"; const envSqliteFile = process.env.DB_SQLITE_FILE || '/data/database.sqlite';
logger.info(`Using Sqlite: ${envSqliteFile}`); logger.info(`Using Sqlite: ${envSqliteFile}`);
instance = { instance = {
database: { database: {
engine: "knex-native", engine: 'knex-native',
knex: { knex: {
client: sqliteClientName, client: sqliteClientName,
connection: { connection: {
filename: envSqliteFile, filename: envSqliteFile
},
useNullAsDefault: true,
}, },
useNullAsDefault: true
}
}, },
keys: getKeys(), keys: getKeys(),
}; };
@@ -90,55 +88,54 @@ const configure = () => {
const getKeys = () => { const getKeys = () => {
// Get keys from file // Get keys from file
logger.debug("Cheecking for keys file:", keysFile);
if (!fs.existsSync(keysFile)) { if (!fs.existsSync(keysFile)) {
generateKeys(); generateKeys();
} else if (process.env.DEBUG) { } else if (process.env.DEBUG) {
logger.info("Keys file exists OK"); logger.info('Keys file exists OK');
} }
try { try {
// Load this json keysFile synchronously and return the json object return require(keysFile);
const rawData = fs.readFileSync(keysFile);
return JSON.parse(rawData);
} catch (err) { } catch (err) {
logger.error(`Could not read JWT key pair from config file: ${keysFile}`, err); logger.error('Could not read JWT key pair from config file: ' + keysFile, err);
process.exit(1); process.exit(1);
} }
}; };
const generateKeys = () => { const generateKeys = () => {
logger.info("Creating a new JWT key pair..."); logger.info('Creating a new JWT key pair...');
// Now create the keys and save them in the config. // Now create the keys and save them in the config.
const key = new NodeRSA({ b: 2048 }); const key = new NodeRSA({ b: 2048 });
key.generateKeyPair(); key.generateKeyPair();
const keys = { const keys = {
key: key.exportKey("private").toString(), key: key.exportKey('private').toString(),
pub: key.exportKey("public").toString(), pub: key.exportKey('public').toString(),
}; };
// Write keys config // Write keys config
try { try {
fs.writeFileSync(keysFile, JSON.stringify(keys, null, 2)); fs.writeFileSync(keysFile, JSON.stringify(keys, null, 2));
} catch (err) { } catch (err) {
logger.error(`Could not write JWT key pair to config file: ${keysFile}: ${err.message}`); logger.error('Could not write JWT key pair to config file: ' + keysFile + ': ' + err.message);
process.exit(1); process.exit(1);
} }
logger.info(`Wrote JWT key pair to config file: ${keysFile}`); logger.info('Wrote JWT key pair to config file: ' + keysFile);
}; };
module.exports = {
/** /**
* *
* @param {string} key ie: 'database' or 'database.engine' * @param {string} key ie: 'database' or 'database.engine'
* @returns {boolean} * @returns {boolean}
*/ */
const configHas = (key) => { has: function(key) {
instance === null && configure(); instance === null && configure();
const keys = key.split("."); const keys = key.split('.');
let level = instance; let level = instance;
let has = true; let has = true;
keys.forEach((keyItem) =>{ keys.forEach((keyItem) =>{
if (typeof level[keyItem] === "undefined") { if (typeof level[keyItem] === 'undefined') {
has = false; has = false;
} else { } else {
level = level[keyItem]; level = level[keyItem];
@@ -146,7 +143,7 @@ const configHas = (key) => {
}); });
return has; return has;
}; },
/** /**
* Gets a specific key from the top level * Gets a specific key from the top level
@@ -154,91 +151,87 @@ const configHas = (key) => {
* @param {string} key * @param {string} key
* @returns {*} * @returns {*}
*/ */
const configGet = (key) => { get: function (key) {
instance === null && configure(); instance === null && configure();
if (key && typeof instance[key] !== "undefined") { if (key && typeof instance[key] !== 'undefined') {
return instance[key]; return instance[key];
} }
return instance; return instance;
}; },
/** /**
* Is this a sqlite configuration? * Is this a sqlite configuration?
* *
* @returns {boolean} * @returns {boolean}
*/ */
const isSqlite = () => { isSqlite: function () {
instance === null && configure(); instance === null && configure();
return instance.database.knex && instance.database.knex.client === sqliteClientName; return instance.database.knex && instance.database.knex.client === sqliteClientName;
}; },
/** /**
* Is this a mysql configuration? * Is this a mysql configuration?
* *
* @returns {boolean} * @returns {boolean}
*/ */
const isMysql = () => { isMysql: function () {
instance === null && configure(); instance === null && configure();
return instance.database.engine === mysqlEngine; return instance.database.engine === mysqlEngine;
}; },
/** /**
* Is this a postgres configuration? * Is this a postgres configuration?
* *
* @returns {boolean} * @returns {boolean}
*/ */
const isPostgres = () => { isPostgres: function () {
instance === null && configure(); instance === null && configure();
return instance.database.engine === postgresEngine; return instance.database.engine === postgresEngine;
}; },
/** /**
* Are we running in debug mdoe? * Are we running in debug mdoe?
* *
* @returns {boolean} * @returns {boolean}
*/ */
const isDebugMode = () => !!process.env.DEBUG; debug: function () {
return !!process.env.DEBUG;
/** },
* Are we running in CI?
*
* @returns {boolean}
*/
const isCI = () => process.env.CI === 'true' && process.env.DEBUG === 'true';
/** /**
* Returns a public key * Returns a public key
* *
* @returns {string} * @returns {string}
*/ */
const getPublicKey = () => { getPublicKey: function () {
instance === null && configure(); instance === null && configure();
return instance.keys.pub; return instance.keys.pub;
}; },
/** /**
* Returns a private key * Returns a private key
* *
* @returns {string} * @returns {string}
*/ */
const getPrivateKey = () => { getPrivateKey: function () {
instance === null && configure(); instance === null && configure();
return instance.keys.key; return instance.keys.key;
}; },
/** /**
* @returns {boolean} * @returns {boolean}
*/ */
const useLetsencryptStaging = () => !!process.env.LE_STAGING; useLetsencryptStaging: function () {
return !!process.env.LE_STAGING;
},
/** /**
* @returns {string|null} * @returns {string|null}
*/ */
const useLetsencryptServer = () => { useLetsencryptServer: function () {
if (process.env.LE_SERVER) { if (process.env.LE_SERVER) {
return process.env.LE_SERVER; return process.env.LE_SERVER;
} }
return null; return null;
}
}; };
export { isCI, configHas, configGet, isSqlite, isMysql, isPostgres, isDebugMode, getPrivateKey, getPublicKey, useLetsencryptStaging, useLetsencryptServer };

View File

@@ -1,11 +1,13 @@
import _ from "lodash"; const _ = require('lodash');
const util = require('util');
const errs = { module.exports = {
PermissionError: function (_, previous) {
PermissionError: function (message, previous) {
Error.captureStackTrace(this, this.constructor); Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name; this.name = this.constructor.name;
this.previous = previous; this.previous = previous;
this.message = "Permission Denied"; this.message = 'Permission Denied';
this.public = true; this.public = true;
this.status = 403; this.status = 403;
}, },
@@ -14,22 +16,18 @@ const errs = {
Error.captureStackTrace(this, this.constructor); Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name; this.name = this.constructor.name;
this.previous = previous; this.previous = previous;
this.message = "Not Found"; this.message = 'Item Not Found - ' + id;
if (id) {
this.message = `Not Found - ${id}`;
}
this.public = true; this.public = true;
this.status = 404; this.status = 404;
}, },
AuthError: function (message, messageI18n, previous) { AuthError: function (message, previous) {
Error.captureStackTrace(this, this.constructor); Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name; this.name = this.constructor.name;
this.previous = previous; this.previous = previous;
this.message = message; this.message = message;
this.message_i18n = messageI18n;
this.public = true; this.public = true;
this.status = 400; this.status = 401;
}, },
InternalError: function (message, previous) { InternalError: function (message, previous) {
@@ -96,8 +94,6 @@ const errs = {
}, },
}; };
_.forEach(errs, (err) => { _.forEach(module.exports, function (error) {
err.prototype = Object.create(Error.prototype); util.inherits(error, Error);
}); });
export default errs;

View File

@@ -1,13 +1,12 @@
export default (req, res, next) => { module.exports = function (req, res, next) {
if (req.headers.origin) { if (req.headers.origin) {
res.set({ res.set({
"Access-Control-Allow-Origin": req.headers.origin, 'Access-Control-Allow-Origin': req.headers.origin,
"Access-Control-Allow-Credentials": true, 'Access-Control-Allow-Credentials': true,
"Access-Control-Allow-Methods": "OPTIONS, GET, POST", 'Access-Control-Allow-Methods': 'OPTIONS, GET, POST',
"Access-Control-Allow-Headers": 'Access-Control-Allow-Headers': 'Content-Type, Cache-Control, Pragma, Expires, Authorization, X-Dataset-Total, X-Dataset-Offset, X-Dataset-Limit',
"Content-Type, Cache-Control, Pragma, Expires, Authorization, X-Dataset-Total, X-Dataset-Offset, X-Dataset-Limit", 'Access-Control-Max-Age': 5 * 60,
"Access-Control-Max-Age": 5 * 60, 'Access-Control-Expose-Headers': 'X-Dataset-Total, X-Dataset-Offset, X-Dataset-Limit'
"Access-Control-Expose-Headers": "X-Dataset-Total, X-Dataset-Offset, X-Dataset-Limit",
}); });
next(); next();
} else { } else {

View File

@@ -1,15 +1,15 @@
import Access from "../access.js"; const Access = require('../access');
export default () => { module.exports = () => {
return async (_, res, next) => { return function (req, res, next) {
try {
res.locals.access = null; res.locals.access = null;
const access = new Access(res.locals.token || null); let access = new Access(res.locals.token || null);
await access.load(); access.load()
.then(() => {
res.locals.access = access; res.locals.access = access;
next(); next();
} catch (err) { })
next(err); .catch(next);
}
}; };
}; };

View File

@@ -1,13 +1,13 @@
export default function () { module.exports = function () {
return (req, res, next) => { return function (req, res, next) {
if (req.headers.authorization) { if (req.headers.authorization) {
const parts = req.headers.authorization.split(" "); let parts = req.headers.authorization.split(' ');
if (parts && parts[0] === "Bearer" && parts[1]) { if (parts && parts[0] === 'Bearer' && parts[1]) {
res.locals.token = parts[1]; res.locals.token = parts[1];
} }
} }
next(); next();
}; };
} };

View File

@@ -1,6 +1,7 @@
import _ from "lodash"; let _ = require('lodash');
module.exports = function (default_sort, default_offset, default_limit, max_limit) {
export default (default_sort, default_offset, default_limit, max_limit) => {
/** /**
* This will setup the req query params with filtered data and defaults * This will setup the req query params with filtered data and defaults
* *
@@ -10,35 +11,34 @@ export default (default_sort, default_offset, default_limit, max_limit) => {
* *
*/ */
return (req, _res, next) => { return function (req, res, next) {
req.query.offset =
typeof req.query.limit === "undefined" ? default_offset || 0 : Number.parseInt(req.query.offset, 10); req.query.offset = typeof req.query.limit === 'undefined' ? default_offset || 0 : parseInt(req.query.offset, 10);
req.query.limit = req.query.limit = typeof req.query.limit === 'undefined' ? default_limit || 50 : parseInt(req.query.limit, 10);
typeof req.query.limit === "undefined" ? default_limit || 50 : Number.parseInt(req.query.limit, 10);
if (max_limit && req.query.limit > max_limit) { if (max_limit && req.query.limit > max_limit) {
req.query.limit = max_limit; req.query.limit = max_limit;
} }
// Sorting // Sorting
let sort = typeof req.query.sort === "undefined" ? default_sort : req.query.sort; let sort = typeof req.query.sort === 'undefined' ? default_sort : req.query.sort;
const myRegexp = /.*\.(asc|desc)$/gi; let myRegexp = /.*\.(asc|desc)$/ig;
const sort_array = []; let sort_array = [];
sort = sort.split(","); sort = sort.split(',');
_.map(sort, (val) => { _.map(sort, function (val) {
const matches = myRegexp.exec(val); let matches = myRegexp.exec(val);
if (matches !== null) { if (matches !== null) {
const dir = matches[1]; let dir = matches[1];
sort_array.push({ sort_array.push({
field: val.substr(0, val.length - (dir.length + 1)), field: val.substr(0, val.length - (dir.length + 1)),
dir: dir.toLowerCase(), dir: dir.toLowerCase()
}); });
} else { } else {
sort_array.push({ sort_array.push({
field: val, field: val,
dir: "asc", dir: 'asc'
}); });
} }
}); });

View File

@@ -1,8 +1,9 @@
export default (req, res, next) => { module.exports = (req, res, next) => {
if (req.params.user_id === 'me' && res.locals.access) { if (req.params.user_id === 'me' && res.locals.access) {
req.params.user_id = res.locals.access.token.get('attrs').id; req.params.user_id = res.locals.access.token.get('attrs').id;
} else { } else {
req.params.user_id = Number.parseInt(req.params.user_id, 10); req.params.user_id = parseInt(req.params.user_id, 10);
} }
next(); next();
}; };

View File

@@ -1,6 +1,8 @@
import moment from "moment"; const moment = require('moment');
import { ref } from "objection"; const {isPostgres} = require('./config');
import { isPostgres } from "./config.js"; const {ref} = require('objection');
module.exports = {
/** /**
* Takes an expression such as 30d and returns a moment object of that date in future * Takes an expression such as 30d and returns a moment object of that date in future
@@ -20,32 +22,32 @@ import { isPostgres } from "./config.js";
* @param {String} expression * @param {String} expression
* @returns {Object} * @returns {Object}
*/ */
const parseDatePeriod = (expression) => { parseDatePeriod: function (expression) {
const matches = expression.match(/^([0-9]+)(y|Q|M|w|d|h|m|s|ms)$/m); let matches = expression.match(/^([0-9]+)(y|Q|M|w|d|h|m|s|ms)$/m);
if (matches) { if (matches) {
return moment().add(matches[1], matches[2]); return moment().add(matches[1], matches[2]);
} }
return null; return null;
}; },
const convertIntFieldsToBool = (obj, fields) => { convertIntFieldsToBool: function (obj, fields) {
fields.forEach((field) => { fields.forEach(function (field) {
if (typeof obj[field] !== "undefined") { if (typeof obj[field] !== 'undefined') {
obj[field] = obj[field] === 1; obj[field] = obj[field] === 1;
} }
}); });
return obj; return obj;
}; },
const convertBoolFieldsToInt = (obj, fields) => { convertBoolFieldsToInt: function (obj, fields) {
fields.forEach((field) => { fields.forEach(function (field) {
if (typeof obj[field] !== "undefined") { if (typeof obj[field] !== 'undefined') {
obj[field] = obj[field] ? 1 : 0; obj[field] = obj[field] ? 1 : 0;
} }
}); });
return obj; return obj;
}; },
/** /**
* Casts a column to json if using postgres * Casts a column to json if using postgres
@@ -53,6 +55,8 @@ const convertBoolFieldsToInt = (obj, fields) => {
* @param {string} colName * @param {string} colName
* @returns {string|Objection.ReferenceBuilder} * @returns {string|Objection.ReferenceBuilder}
*/ */
const castJsonIfNeed = (colName) => (isPostgres() ? ref(colName).castText() : colName); castJsonIfNeed: function (colName) {
return isPostgres() ? ref(colName).castText() : colName;
}
export { parseDatePeriod, convertIntFieldsToBool, convertBoolFieldsToInt, castJsonIfNeed }; };

View File

@@ -1,6 +1,5 @@
import { migrate as logger } from "../logger.js"; const migrate_name = 'identifier_for_migrate';
const logger = require('../logger').migrate;
const migrateName = "identifier_for_migrate";
/** /**
* Migrate * Migrate
@@ -8,15 +7,16 @@ const migrateName = "identifier_for_migrate";
* @see http://knexjs.org/#Schema * @see http://knexjs.org/#Schema
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const up = (_knex) => { exports.up = function (knex, Promise) {
logger.info(`[${migrateName}] Migrating Up...`);
logger.info('[' + migrate_name + '] Migrating Up...');
// Create Table example: // Create Table example:
/* /*return knex.schema.createTable('notification', (table) => {
return knex.schema.createTable('notification', (table) => {
table.increments().primary(); table.increments().primary();
table.string('name').notNull(); table.string('name').notNull();
table.string('type').notNull(); table.string('type').notNull();
@@ -24,11 +24,10 @@ const up = (_knex) => {
table.integer('modified_on').notNull(); table.integer('modified_on').notNull();
}) })
.then(function () { .then(function () {
logger.info('[' + migrateName + '] Notification Table created'); logger.info('[' + migrate_name + '] Notification Table created');
}); });*/
*/
logger.info(`[${migrateName}] Migrating Up Complete`); logger.info('[' + migrate_name + '] Migrating Up Complete');
return Promise.resolve(true); return Promise.resolve(true);
}; };
@@ -37,23 +36,20 @@ const up = (_knex) => {
* Undo Migrate * Undo Migrate
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const down = (_knex) => { exports.down = function (knex, Promise) {
logger.info(`[${migrateName}] Migrating Down...`); logger.info('[' + migrate_name + '] Migrating Down...');
// Drop table example: // Drop table example:
/* /*return knex.schema.dropTable('notification')
return knex.schema.dropTable('notification')
.then(() => { .then(() => {
logger.info(`[${migrateName}] Notification Table dropped`); logger.info('[' + migrate_name + '] Notification Table dropped');
}); });*/
*/
logger.info(`[${migrateName}] Migrating Down Complete`); logger.info('[' + migrate_name + '] Migrating Down Complete');
return Promise.resolve(true); return Promise.resolve(true);
}; };
export { up, down };

View File

@@ -1,52 +1,49 @@
import { exec as nodeExec, execFile as nodeExecFile } from "node:child_process"; const _ = require('lodash');
import { dirname } from "node:path"; const exec = require('node:child_process').exec;
import { fileURLToPath } from "node:url"; const execFile = require('node:child_process').execFile;
import { Liquid } from "liquidjs"; const { Liquid } = require('liquidjs');
import _ from "lodash"; const logger = require('../logger').global;
import { global as logger } from "../logger.js"; const error = require('./error');
import errs from "./error.js";
const __filename = fileURLToPath(import.meta.url); module.exports = {
const __dirname = dirname(__filename);
exec: async (cmd, options = {}) => {
logger.debug('CMD:', cmd);
const exec = async (cmd, options = {}) => {
logger.debug("CMD:", cmd);
const { stdout, stderr } = await new Promise((resolve, reject) => { const { stdout, stderr } = await new Promise((resolve, reject) => {
const child = nodeExec(cmd, options, (isError, stdout, stderr) => { const child = exec(cmd, options, (isError, stdout, stderr) => {
if (isError) { if (isError) {
reject(new errs.CommandError(stderr, isError)); reject(new error.CommandError(stderr, isError));
} else { } else {
resolve({ stdout, stderr }); resolve({ stdout, stderr });
} }
}); });
child.on("error", (e) => { child.on('error', (e) => {
reject(new errs.CommandError(stderr, 1, e)); reject(new error.CommandError(stderr, 1, e));
}); });
}); });
return stdout; return stdout;
}; },
/** /**
* @param {String} cmd * @param {String} cmd
* @param {Array} args * @param {Array} args
* @param {Object|undefined} options
* @returns {Promise} * @returns {Promise}
*/ */
const execFile = (cmd, args, options) => { execFile: (cmd, args) => {
logger.debug(`CMD: ${cmd} ${args ? args.join(" ") : ""}`); // logger.debug('CMD: ' + cmd + ' ' + (args ? args.join(' ') : ''));
const opts = options || {};
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
nodeExecFile(cmd, args, opts, (err, stdout, stderr) => { execFile(cmd, args, (err, stdout, /*stderr*/) => {
if (err && typeof err === "object") { if (err && typeof err === 'object') {
reject(new errs.CommandError(stderr, 1, err)); reject(err);
} else { } else {
resolve(stdout.trim()); resolve(stdout.trim());
} }
}); });
}); });
}; },
/** /**
* Used in objection query builder * Used in objection query builder
@@ -54,7 +51,7 @@ const execFile = (cmd, args, options) => {
* @param {Array} omissions * @param {Array} omissions
* @returns {Function} * @returns {Function}
*/ */
const omitRow = (omissions) => { omitRow: (omissions) => {
/** /**
* @param {Object} row * @param {Object} row
* @returns {Object} * @returns {Object}
@@ -62,7 +59,7 @@ const omitRow = (omissions) => {
return (row) => { return (row) => {
return _.omit(row, omissions); return _.omit(row, omissions);
}; };
}; },
/** /**
* Used in objection query builder * Used in objection query builder
@@ -70,7 +67,7 @@ const omitRow = (omissions) => {
* @param {Array} omissions * @param {Array} omissions
* @returns {Function} * @returns {Function}
*/ */
const omitRows = (omissions) => { omitRows: (omissions) => {
/** /**
* @param {Array} rows * @param {Array} rows
* @returns {Object} * @returns {Object}
@@ -81,14 +78,14 @@ const omitRows = (omissions) => {
}); });
return rows; return rows;
}; };
}; },
/** /**
* @returns {Object} Liquid render engine * @returns {Object} Liquid render engine
*/ */
const getRenderEngine = () => { getRenderEngine: () => {
const renderEngine = new Liquid({ const renderEngine = new Liquid({
root: `${__dirname}/../templates/`, root: `${__dirname}/../templates/`
}); });
/** /**
@@ -97,14 +94,13 @@ const getRenderEngine = () => {
* directive string * directive string
* address string * address string
*/ */
renderEngine.registerFilter("nginxAccessRule", (v) => { renderEngine.registerFilter('nginxAccessRule', (v) => {
if (typeof v.directive !== "undefined" && typeof v.address !== "undefined" && v.directive && v.address) { if (typeof v.directive !== 'undefined' && typeof v.address !== 'undefined' && v.directive && v.address) {
return `${v.directive} ${v.address};`; return `${v.directive} ${v.address};`;
} }
return ""; return '';
}); });
return renderEngine; return renderEngine;
}
}; };
export default { exec, execFile, omitRow, omitRows, getRenderEngine };

View File

@@ -1,5 +1,5 @@
import Ajv from "ajv/dist/2020.js"; const Ajv = require('ajv/dist/2020');
import errs from "../error.js"; const error = require('../error');
const ajv = new Ajv({ const ajv = new Ajv({
verbose: true, verbose: true,
@@ -14,27 +14,30 @@ const ajv = new Ajv({
* @param {Object} payload * @param {Object} payload
* @returns {Promise} * @returns {Promise}
*/ */
const apiValidator = async (schema, payload /*, description*/) => { function apiValidator (schema, payload/*, description*/) {
if (!schema) { return new Promise(function Promise_apiValidator (resolve, reject) {
throw new errs.ValidationError("Schema is undefined"); if (schema === null) {
reject(new error.ValidationError('Schema is undefined'));
return;
} }
// Can't use falsy check here as valid payload could be `0` or `false` if (typeof payload === 'undefined') {
if (typeof payload === "undefined") { reject(new error.ValidationError('Payload is undefined'));
throw new errs.ValidationError("Payload is undefined"); return;
} }
const validate = ajv.compile(schema); const validate = ajv.compile(schema);
const valid = validate(payload); const valid = validate(payload);
if (valid && !validate.errors) { if (valid && !validate.errors) {
return payload; resolve(payload);
} else {
let message = ajv.errorsText(validate.errors);
let err = new error.ValidationError(message);
err.debug = [validate.errors, payload];
reject(err);
}
});
} }
const message = ajv.errorsText(validate.errors); module.exports = apiValidator;
const err = new errs.ValidationError(message);
err.debug = [validate.errors, payload];
throw err;
};
export default apiValidator;

View File

@@ -1,7 +1,7 @@
import Ajv from 'ajv/dist/2020.js'; const _ = require('lodash');
import _ from "lodash"; const Ajv = require('ajv/dist/2020');
import commonDefinitions from "../../schema/common.json" with { type: "json" }; const error = require('../error');
import errs from "../error.js"; const commonDefinitions = require('../../schema/common.json');
RegExp.prototype.toJSON = RegExp.prototype.toString; RegExp.prototype.toJSON = RegExp.prototype.toString;
@@ -11,7 +11,7 @@ const ajv = new Ajv({
allowUnionTypes: true, allowUnionTypes: true,
coerceTypes: true, coerceTypes: true,
strict: false, strict: false,
schemas: [commonDefinitions], schemas: [commonDefinitions]
}); });
/** /**
@@ -20,26 +20,26 @@ const ajv = new Ajv({
* @param {Object} payload * @param {Object} payload
* @returns {Promise} * @returns {Promise}
*/ */
const validator = (schema, payload) => { function validator (schema, payload) {
return new Promise((resolve, reject) => { return new Promise(function (resolve, reject) {
if (!payload) { if (!payload) {
reject(new errs.InternalValidationError("Payload is falsy")); reject(new error.InternalValidationError('Payload is falsy'));
} else { } else {
try { try {
const validate = ajv.compile(schema); let validate = ajv.compile(schema);
const valid = validate(payload); let valid = validate(payload);
if (valid && !validate.errors) { if (valid && !validate.errors) {
resolve(_.cloneDeep(payload)); resolve(_.cloneDeep(payload));
} else { } else {
const message = ajv.errorsText(validate.errors); let message = ajv.errorsText(validate.errors);
reject(new errs.InternalValidationError(message)); reject(new error.InternalValidationError(message));
} }
} catch (err) { } catch (err) {
reject(err); reject(err);
} }
} }
}); });
}; }
export default validator; module.exports = validator;

View File

@@ -1,18 +1,14 @@
import signale from "signale"; const {Signale} = require('signale');
const opts = { module.exports = {
logLevel: "info", global: new Signale({scope: 'Global '}),
migrate: new Signale({scope: 'Migrate '}),
express: new Signale({scope: 'Express '}),
access: new Signale({scope: 'Access '}),
nginx: new Signale({scope: 'Nginx '}),
ssl: new Signale({scope: 'SSL '}),
certbot: new Signale({scope: 'Certbot '}),
import: new Signale({scope: 'Importer '}),
setup: new Signale({scope: 'Setup '}),
ip_ranges: new Signale({scope: 'IP Ranges'})
}; };
const global = new signale.Signale({ scope: "Global ", ...opts });
const migrate = new signale.Signale({ scope: "Migrate ", ...opts });
const express = new signale.Signale({ scope: "Express ", ...opts });
const access = new signale.Signale({ scope: "Access ", ...opts });
const nginx = new signale.Signale({ scope: "Nginx ", ...opts });
const ssl = new signale.Signale({ scope: "SSL ", ...opts });
const certbot = new signale.Signale({ scope: "Certbot ", ...opts });
const importer = new signale.Signale({ scope: "Importer ", ...opts });
const setup = new signale.Signale({ scope: "Setup ", ...opts });
const ipRanges = new signale.Signale({ scope: "IP Ranges", ...opts });
export { global, migrate, express, access, nginx, ssl, certbot, importer, setup, ipRanges };

View File

@@ -1,13 +1,15 @@
import db from "./db.js"; const db = require('./db');
import { migrate as logger } from "./logger.js"; const logger = require('./logger').migrate;
const migrateUp = async () => { module.exports = {
const version = await db.migrate.currentVersion(); latest: function () {
logger.info("Current database version:", version); return db.migrate.currentVersion()
return await db.migrate.latest({ .then((version) => {
tableName: "migrations", logger.info('Current database version:', version);
directory: "migrations", return db.migrate.latest({
tableName: 'migrations',
directory: 'migrations'
}); });
});
}
}; };
export { migrateUp };

View File

@@ -1,6 +1,5 @@
import { migrate as logger } from "../logger.js"; const migrate_name = 'initial-schema';
const logger = require('../logger').migrate;
const migrateName = "initial-schema";
/** /**
* Migrate * Migrate
@@ -8,199 +7,199 @@ const migrateName = "initial-schema";
* @see http://knexjs.org/#Schema * @see http://knexjs.org/#Schema
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const up = (knex) => { exports.up = function (knex/*, Promise*/) {
logger.info(`[${migrateName}] Migrating Up...`); logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema return knex.schema.createTable('auth', (table) => {
.createTable("auth", (table) => {
table.increments().primary(); table.increments().primary();
table.dateTime("created_on").notNull(); table.dateTime('created_on').notNull();
table.dateTime("modified_on").notNull(); table.dateTime('modified_on').notNull();
table.integer("user_id").notNull().unsigned(); table.integer('user_id').notNull().unsigned();
table.string("type", 30).notNull(); table.string('type', 30).notNull();
table.string("secret").notNull(); table.string('secret').notNull();
table.json("meta").notNull(); table.json('meta').notNull();
table.integer("is_deleted").notNull().unsigned().defaultTo(0); table.integer('is_deleted').notNull().unsigned().defaultTo(0);
}) })
.then(() => { .then(() => {
logger.info(`[${migrateName}] auth Table created`); logger.info('[' + migrate_name + '] auth Table created');
return knex.schema.createTable("user", (table) => { return knex.schema.createTable('user', (table) => {
table.increments().primary(); table.increments().primary();
table.dateTime("created_on").notNull(); table.dateTime('created_on').notNull();
table.dateTime("modified_on").notNull(); table.dateTime('modified_on').notNull();
table.integer("is_deleted").notNull().unsigned().defaultTo(0); table.integer('is_deleted').notNull().unsigned().defaultTo(0);
table.integer("is_disabled").notNull().unsigned().defaultTo(0); table.integer('is_disabled').notNull().unsigned().defaultTo(0);
table.string("email").notNull(); table.string('email').notNull();
table.string("name").notNull(); table.string('name').notNull();
table.string("nickname").notNull(); table.string('nickname').notNull();
table.string("avatar").notNull(); table.string('avatar').notNull();
table.json("roles").notNull(); table.json('roles').notNull();
}); });
}) })
.then(() => { .then(() => {
logger.info(`[${migrateName}] user Table created`); logger.info('[' + migrate_name + '] user Table created');
return knex.schema.createTable("user_permission", (table) => { return knex.schema.createTable('user_permission', (table) => {
table.increments().primary(); table.increments().primary();
table.dateTime("created_on").notNull(); table.dateTime('created_on').notNull();
table.dateTime("modified_on").notNull(); table.dateTime('modified_on').notNull();
table.integer("user_id").notNull().unsigned(); table.integer('user_id').notNull().unsigned();
table.string("visibility").notNull(); table.string('visibility').notNull();
table.string("proxy_hosts").notNull(); table.string('proxy_hosts').notNull();
table.string("redirection_hosts").notNull(); table.string('redirection_hosts').notNull();
table.string("dead_hosts").notNull(); table.string('dead_hosts').notNull();
table.string("streams").notNull(); table.string('streams').notNull();
table.string("access_lists").notNull(); table.string('access_lists').notNull();
table.string("certificates").notNull(); table.string('certificates').notNull();
table.unique("user_id"); table.unique('user_id');
}); });
}) })
.then(() => { .then(() => {
logger.info(`[${migrateName}] user_permission Table created`); logger.info('[' + migrate_name + '] user_permission Table created');
return knex.schema.createTable("proxy_host", (table) => { return knex.schema.createTable('proxy_host', (table) => {
table.increments().primary(); table.increments().primary();
table.dateTime("created_on").notNull(); table.dateTime('created_on').notNull();
table.dateTime("modified_on").notNull(); table.dateTime('modified_on').notNull();
table.integer("owner_user_id").notNull().unsigned(); table.integer('owner_user_id').notNull().unsigned();
table.integer("is_deleted").notNull().unsigned().defaultTo(0); table.integer('is_deleted').notNull().unsigned().defaultTo(0);
table.json("domain_names").notNull(); table.json('domain_names').notNull();
table.string("forward_ip").notNull(); table.string('forward_ip').notNull();
table.integer("forward_port").notNull().unsigned(); table.integer('forward_port').notNull().unsigned();
table.integer("access_list_id").notNull().unsigned().defaultTo(0); table.integer('access_list_id').notNull().unsigned().defaultTo(0);
table.integer("certificate_id").notNull().unsigned().defaultTo(0); table.integer('certificate_id').notNull().unsigned().defaultTo(0);
table.integer("ssl_forced").notNull().unsigned().defaultTo(0); table.integer('ssl_forced').notNull().unsigned().defaultTo(0);
table.integer("caching_enabled").notNull().unsigned().defaultTo(0); table.integer('caching_enabled').notNull().unsigned().defaultTo(0);
table.integer("block_exploits").notNull().unsigned().defaultTo(0); table.integer('block_exploits').notNull().unsigned().defaultTo(0);
table.text("advanced_config").notNull().defaultTo(""); table.text('advanced_config').notNull().defaultTo('');
table.json("meta").notNull(); table.json('meta').notNull();
}); });
}) })
.then(() => { .then(() => {
logger.info(`[${migrateName}] proxy_host Table created`); logger.info('[' + migrate_name + '] proxy_host Table created');
return knex.schema.createTable("redirection_host", (table) => { return knex.schema.createTable('redirection_host', (table) => {
table.increments().primary(); table.increments().primary();
table.dateTime("created_on").notNull(); table.dateTime('created_on').notNull();
table.dateTime("modified_on").notNull(); table.dateTime('modified_on').notNull();
table.integer("owner_user_id").notNull().unsigned(); table.integer('owner_user_id').notNull().unsigned();
table.integer("is_deleted").notNull().unsigned().defaultTo(0); table.integer('is_deleted').notNull().unsigned().defaultTo(0);
table.json("domain_names").notNull(); table.json('domain_names').notNull();
table.string("forward_domain_name").notNull(); table.string('forward_domain_name').notNull();
table.integer("preserve_path").notNull().unsigned().defaultTo(0); table.integer('preserve_path').notNull().unsigned().defaultTo(0);
table.integer("certificate_id").notNull().unsigned().defaultTo(0); table.integer('certificate_id').notNull().unsigned().defaultTo(0);
table.integer("ssl_forced").notNull().unsigned().defaultTo(0); table.integer('ssl_forced').notNull().unsigned().defaultTo(0);
table.integer("block_exploits").notNull().unsigned().defaultTo(0); table.integer('block_exploits').notNull().unsigned().defaultTo(0);
table.text("advanced_config").notNull().defaultTo(""); table.text('advanced_config').notNull().defaultTo('');
table.json("meta").notNull(); table.json('meta').notNull();
}); });
}) })
.then(() => { .then(() => {
logger.info(`[${migrateName}] redirection_host Table created`); logger.info('[' + migrate_name + '] redirection_host Table created');
return knex.schema.createTable("dead_host", (table) => { return knex.schema.createTable('dead_host', (table) => {
table.increments().primary(); table.increments().primary();
table.dateTime("created_on").notNull(); table.dateTime('created_on').notNull();
table.dateTime("modified_on").notNull(); table.dateTime('modified_on').notNull();
table.integer("owner_user_id").notNull().unsigned(); table.integer('owner_user_id').notNull().unsigned();
table.integer("is_deleted").notNull().unsigned().defaultTo(0); table.integer('is_deleted').notNull().unsigned().defaultTo(0);
table.json("domain_names").notNull(); table.json('domain_names').notNull();
table.integer("certificate_id").notNull().unsigned().defaultTo(0); table.integer('certificate_id').notNull().unsigned().defaultTo(0);
table.integer("ssl_forced").notNull().unsigned().defaultTo(0); table.integer('ssl_forced').notNull().unsigned().defaultTo(0);
table.text("advanced_config").notNull().defaultTo(""); table.text('advanced_config').notNull().defaultTo('');
table.json("meta").notNull(); table.json('meta').notNull();
}); });
}) })
.then(() => { .then(() => {
logger.info(`[${migrateName}] dead_host Table created`); logger.info('[' + migrate_name + '] dead_host Table created');
return knex.schema.createTable("stream", (table) => { return knex.schema.createTable('stream', (table) => {
table.increments().primary(); table.increments().primary();
table.dateTime("created_on").notNull(); table.dateTime('created_on').notNull();
table.dateTime("modified_on").notNull(); table.dateTime('modified_on').notNull();
table.integer("owner_user_id").notNull().unsigned(); table.integer('owner_user_id').notNull().unsigned();
table.integer("is_deleted").notNull().unsigned().defaultTo(0); table.integer('is_deleted').notNull().unsigned().defaultTo(0);
table.integer("incoming_port").notNull().unsigned(); table.integer('incoming_port').notNull().unsigned();
table.string("forward_ip").notNull(); table.string('forward_ip').notNull();
table.integer("forwarding_port").notNull().unsigned(); table.integer('forwarding_port').notNull().unsigned();
table.integer("tcp_forwarding").notNull().unsigned().defaultTo(0); table.integer('tcp_forwarding').notNull().unsigned().defaultTo(0);
table.integer("udp_forwarding").notNull().unsigned().defaultTo(0); table.integer('udp_forwarding').notNull().unsigned().defaultTo(0);
table.json("meta").notNull(); table.json('meta').notNull();
}); });
}) })
.then(() => { .then(() => {
logger.info(`[${migrateName}] stream Table created`); logger.info('[' + migrate_name + '] stream Table created');
return knex.schema.createTable("access_list", (table) => { return knex.schema.createTable('access_list', (table) => {
table.increments().primary(); table.increments().primary();
table.dateTime("created_on").notNull(); table.dateTime('created_on').notNull();
table.dateTime("modified_on").notNull(); table.dateTime('modified_on').notNull();
table.integer("owner_user_id").notNull().unsigned(); table.integer('owner_user_id').notNull().unsigned();
table.integer("is_deleted").notNull().unsigned().defaultTo(0); table.integer('is_deleted').notNull().unsigned().defaultTo(0);
table.string("name").notNull(); table.string('name').notNull();
table.json("meta").notNull(); table.json('meta').notNull();
}); });
}) })
.then(() => { .then(() => {
logger.info(`[${migrateName}] access_list Table created`); logger.info('[' + migrate_name + '] access_list Table created');
return knex.schema.createTable("certificate", (table) => { return knex.schema.createTable('certificate', (table) => {
table.increments().primary(); table.increments().primary();
table.dateTime("created_on").notNull(); table.dateTime('created_on').notNull();
table.dateTime("modified_on").notNull(); table.dateTime('modified_on').notNull();
table.integer("owner_user_id").notNull().unsigned(); table.integer('owner_user_id').notNull().unsigned();
table.integer("is_deleted").notNull().unsigned().defaultTo(0); table.integer('is_deleted').notNull().unsigned().defaultTo(0);
table.string("provider").notNull(); table.string('provider').notNull();
table.string("nice_name").notNull().defaultTo(""); table.string('nice_name').notNull().defaultTo('');
table.json("domain_names").notNull(); table.json('domain_names').notNull();
table.dateTime("expires_on").notNull(); table.dateTime('expires_on').notNull();
table.json("meta").notNull(); table.json('meta').notNull();
}); });
}) })
.then(() => { .then(() => {
logger.info(`[${migrateName}] certificate Table created`); logger.info('[' + migrate_name + '] certificate Table created');
return knex.schema.createTable("access_list_auth", (table) => { return knex.schema.createTable('access_list_auth', (table) => {
table.increments().primary(); table.increments().primary();
table.dateTime("created_on").notNull(); table.dateTime('created_on').notNull();
table.dateTime("modified_on").notNull(); table.dateTime('modified_on').notNull();
table.integer("access_list_id").notNull().unsigned(); table.integer('access_list_id').notNull().unsigned();
table.string("username").notNull(); table.string('username').notNull();
table.string("password").notNull(); table.string('password').notNull();
table.json("meta").notNull(); table.json('meta').notNull();
}); });
}) })
.then(() => { .then(() => {
logger.info(`[${migrateName}] access_list_auth Table created`); logger.info('[' + migrate_name + '] access_list_auth Table created');
return knex.schema.createTable("audit_log", (table) => { return knex.schema.createTable('audit_log', (table) => {
table.increments().primary(); table.increments().primary();
table.dateTime("created_on").notNull(); table.dateTime('created_on').notNull();
table.dateTime("modified_on").notNull(); table.dateTime('modified_on').notNull();
table.integer("user_id").notNull().unsigned(); table.integer('user_id').notNull().unsigned();
table.string("object_type").notNull().defaultTo(""); table.string('object_type').notNull().defaultTo('');
table.integer("object_id").notNull().unsigned().defaultTo(0); table.integer('object_id').notNull().unsigned().defaultTo(0);
table.string("action").notNull(); table.string('action').notNull();
table.json("meta").notNull(); table.json('meta').notNull();
}); });
}) })
.then(() => { .then(() => {
logger.info(`[${migrateName}] audit_log Table created`); logger.info('[' + migrate_name + '] audit_log Table created');
}); });
}; };
/** /**
* Undo Migrate * Undo Migrate
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const down = (_knex) => { exports.down = function (knex, Promise) {
logger.warn(`[${migrateName}] You can't migrate down the initial data.`); logger.warn('[' + migrate_name + '] You can\'t migrate down the initial data.');
return Promise.resolve(true); return Promise.resolve(true);
}; };
export { up, down };

View File

@@ -1,6 +1,5 @@
import { migrate as logger } from "../logger.js"; const migrate_name = 'websockets';
const logger = require('../logger').migrate;
const migrateName = "websockets";
/** /**
* Migrate * Migrate
@@ -8,29 +7,29 @@ const migrateName = "websockets";
* @see http://knexjs.org/#Schema * @see http://knexjs.org/#Schema
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const up = (knex) => { exports.up = function (knex/*, Promise*/) {
logger.info(`[${migrateName}] Migrating Up...`); logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema return knex.schema.table('proxy_host', function (proxy_host) {
.table("proxy_host", (proxy_host) => { proxy_host.integer('allow_websocket_upgrade').notNull().unsigned().defaultTo(0);
proxy_host.integer("allow_websocket_upgrade").notNull().unsigned().defaultTo(0);
}) })
.then(() => { .then(() => {
logger.info(`[${migrateName}] proxy_host Table altered`); logger.info('[' + migrate_name + '] proxy_host Table altered');
}); });
}; };
/** /**
* Undo Migrate * Undo Migrate
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const down = (_knex) => { exports.down = function (knex, Promise) {
logger.warn(`[${migrateName}] You can't migrate down this one.`); logger.warn('[' + migrate_name + '] You can\'t migrate down this one.');
return Promise.resolve(true); return Promise.resolve(true);
}; };
export { up, down };

View File

@@ -1,6 +1,5 @@
import { migrate as logger } from "../logger.js"; const migrate_name = 'forward_host';
const logger = require('../logger').migrate;
const migrateName = "forward_host";
/** /**
* Migrate * Migrate
@@ -8,17 +7,17 @@ const migrateName = "forward_host";
* @see http://knexjs.org/#Schema * @see http://knexjs.org/#Schema
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const up = (knex) => { exports.up = function (knex/*, Promise*/) {
logger.info(`[${migrateName}] Migrating Up...`); logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema return knex.schema.table('proxy_host', function (proxy_host) {
.table("proxy_host", (proxy_host) => { proxy_host.renameColumn('forward_ip', 'forward_host');
proxy_host.renameColumn("forward_ip", "forward_host");
}) })
.then(() => { .then(() => {
logger.info(`[${migrateName}] proxy_host Table altered`); logger.info('[' + migrate_name + '] proxy_host Table altered');
}); });
}; };
@@ -26,11 +25,10 @@ const up = (knex) => {
* Undo Migrate * Undo Migrate
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const down = (_knex) => { exports.down = function (knex, Promise) {
logger.warn(`[${migrateName}] You can't migrate down this one.`); logger.warn('[' + migrate_name + '] You can\'t migrate down this one.');
return Promise.resolve(true); return Promise.resolve(true);
}; };
export { up, down };

View File

@@ -1,6 +1,5 @@
import { migrate as logger } from "../logger.js"; const migrate_name = 'http2_support';
const logger = require('../logger').migrate;
const migrateName = "http2_support";
/** /**
* Migrate * Migrate
@@ -8,31 +7,31 @@ const migrateName = "http2_support";
* @see http://knexjs.org/#Schema * @see http://knexjs.org/#Schema
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const up = (knex) => { exports.up = function (knex/*, Promise*/) {
logger.info(`[${migrateName}] Migrating Up...`); logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema return knex.schema.table('proxy_host', function (proxy_host) {
.table("proxy_host", (proxy_host) => { proxy_host.integer('http2_support').notNull().unsigned().defaultTo(0);
proxy_host.integer("http2_support").notNull().unsigned().defaultTo(0);
}) })
.then(() => { .then(() => {
logger.info(`[${migrateName}] proxy_host Table altered`); logger.info('[' + migrate_name + '] proxy_host Table altered');
return knex.schema.table("redirection_host", (redirection_host) => { return knex.schema.table('redirection_host', function (redirection_host) {
redirection_host.integer("http2_support").notNull().unsigned().defaultTo(0); redirection_host.integer('http2_support').notNull().unsigned().defaultTo(0);
}); });
}) })
.then(() => { .then(() => {
logger.info(`[${migrateName}] redirection_host Table altered`); logger.info('[' + migrate_name + '] redirection_host Table altered');
return knex.schema.table("dead_host", (dead_host) => { return knex.schema.table('dead_host', function (dead_host) {
dead_host.integer("http2_support").notNull().unsigned().defaultTo(0); dead_host.integer('http2_support').notNull().unsigned().defaultTo(0);
}); });
}) })
.then(() => { .then(() => {
logger.info(`[${migrateName}] dead_host Table altered`); logger.info('[' + migrate_name + '] dead_host Table altered');
}); });
}; };
@@ -40,11 +39,11 @@ const up = (knex) => {
* Undo Migrate * Undo Migrate
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const down = (_knex) => { exports.down = function (knex, Promise) {
logger.warn(`[${migrateName}] You can't migrate down this one.`); logger.warn('[' + migrate_name + '] You can\'t migrate down this one.');
return Promise.resolve(true); return Promise.resolve(true);
}; };
export { up, down };

View File

@@ -1,6 +1,5 @@
import { migrate as logger } from "../logger.js"; const migrate_name = 'forward_scheme';
const logger = require('../logger').migrate;
const migrateName = "forward_scheme";
/** /**
* Migrate * Migrate
@@ -8,17 +7,17 @@ const migrateName = "forward_scheme";
* @see http://knexjs.org/#Schema * @see http://knexjs.org/#Schema
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const up = (knex) => { exports.up = function (knex/*, Promise*/) {
logger.info(`[${migrateName}] Migrating Up...`); logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema return knex.schema.table('proxy_host', function (proxy_host) {
.table("proxy_host", (proxy_host) => { proxy_host.string('forward_scheme').notNull().defaultTo('http');
proxy_host.string("forward_scheme").notNull().defaultTo("http");
}) })
.then(() => { .then(() => {
logger.info(`[${migrateName}] proxy_host Table altered`); logger.info('[' + migrate_name + '] proxy_host Table altered');
}); });
}; };
@@ -26,11 +25,10 @@ const up = (knex) => {
* Undo Migrate * Undo Migrate
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const down = (_knex) => { exports.down = function (knex, Promise) {
logger.warn(`[${migrateName}] You can't migrate down this one.`); logger.warn('[' + migrate_name + '] You can\'t migrate down this one.');
return Promise.resolve(true); return Promise.resolve(true);
}; };
export { up, down };

View File

@@ -1,6 +1,5 @@
import { migrate as logger } from "../logger.js"; const migrate_name = 'disabled';
const logger = require('../logger').migrate;
const migrateName = "disabled";
/** /**
* Migrate * Migrate
@@ -8,38 +7,38 @@ const migrateName = "disabled";
* @see http://knexjs.org/#Schema * @see http://knexjs.org/#Schema
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const up = (knex) => { exports.up = function (knex/*, Promise*/) {
logger.info(`[${migrateName}] Migrating Up...`); logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema return knex.schema.table('proxy_host', function (proxy_host) {
.table("proxy_host", (proxy_host) => { proxy_host.integer('enabled').notNull().unsigned().defaultTo(1);
proxy_host.integer("enabled").notNull().unsigned().defaultTo(1);
}) })
.then(() => { .then(() => {
logger.info(`[${migrateName}] proxy_host Table altered`); logger.info('[' + migrate_name + '] proxy_host Table altered');
return knex.schema.table("redirection_host", (redirection_host) => { return knex.schema.table('redirection_host', function (redirection_host) {
redirection_host.integer("enabled").notNull().unsigned().defaultTo(1); redirection_host.integer('enabled').notNull().unsigned().defaultTo(1);
}); });
}) })
.then(() => { .then(() => {
logger.info(`[${migrateName}] redirection_host Table altered`); logger.info('[' + migrate_name + '] redirection_host Table altered');
return knex.schema.table("dead_host", (dead_host) => { return knex.schema.table('dead_host', function (dead_host) {
dead_host.integer("enabled").notNull().unsigned().defaultTo(1); dead_host.integer('enabled').notNull().unsigned().defaultTo(1);
}); });
}) })
.then(() => { .then(() => {
logger.info(`[${migrateName}] dead_host Table altered`); logger.info('[' + migrate_name + '] dead_host Table altered');
return knex.schema.table("stream", (stream) => { return knex.schema.table('stream', function (stream) {
stream.integer("enabled").notNull().unsigned().defaultTo(1); stream.integer('enabled').notNull().unsigned().defaultTo(1);
}); });
}) })
.then(() => { .then(() => {
logger.info(`[${migrateName}] stream Table altered`); logger.info('[' + migrate_name + '] stream Table altered');
}); });
}; };
@@ -47,11 +46,10 @@ const up = (knex) => {
* Undo Migrate * Undo Migrate
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const down = (_knex) => { exports.down = function (knex, Promise) {
logger.warn(`[${migrateName}] You can't migrate down this one.`); logger.warn('[' + migrate_name + '] You can\'t migrate down this one.');
return Promise.resolve(true); return Promise.resolve(true);
}; };
export { up, down };

View File

@@ -1,6 +1,5 @@
import { migrate as logger } from "../logger.js"; const migrate_name = 'custom_locations';
const logger = require('../logger').migrate;
const migrateName = "custom_locations";
/** /**
* Migrate * Migrate
@@ -9,17 +8,17 @@ const migrateName = "custom_locations";
* @see http://knexjs.org/#Schema * @see http://knexjs.org/#Schema
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const up = (knex) => { exports.up = function (knex/*, Promise*/) {
logger.info(`[${migrateName}] Migrating Up...`); logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema return knex.schema.table('proxy_host', function (proxy_host) {
.table("proxy_host", (proxy_host) => { proxy_host.json('locations');
proxy_host.json("locations");
}) })
.then(() => { .then(() => {
logger.info(`[${migrateName}] proxy_host Table altered`); logger.info('[' + migrate_name + '] proxy_host Table altered');
}); });
}; };
@@ -27,11 +26,10 @@ const up = (knex) => {
* Undo Migrate * Undo Migrate
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const down = (_knex) => { exports.down = function (knex, Promise) {
logger.warn(`[${migrateName}] You can't migrate down this one.`); logger.warn('[' + migrate_name + '] You can\'t migrate down this one.');
return Promise.resolve(true); return Promise.resolve(true);
}; };
export { up, down };

View File

@@ -1,6 +1,5 @@
import { migrate as logger } from "../logger.js"; const migrate_name = 'hsts';
const logger = require('../logger').migrate;
const migrateName = "hsts";
/** /**
* Migrate * Migrate
@@ -8,34 +7,34 @@ const migrateName = "hsts";
* @see http://knexjs.org/#Schema * @see http://knexjs.org/#Schema
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const up = (knex) => { exports.up = function (knex/*, Promise*/) {
logger.info(`[${migrateName}] Migrating Up...`); logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema return knex.schema.table('proxy_host', function (proxy_host) {
.table("proxy_host", (proxy_host) => { proxy_host.integer('hsts_enabled').notNull().unsigned().defaultTo(0);
proxy_host.integer("hsts_enabled").notNull().unsigned().defaultTo(0); proxy_host.integer('hsts_subdomains').notNull().unsigned().defaultTo(0);
proxy_host.integer("hsts_subdomains").notNull().unsigned().defaultTo(0);
}) })
.then(() => { .then(() => {
logger.info(`[${migrateName}] proxy_host Table altered`); logger.info('[' + migrate_name + '] proxy_host Table altered');
return knex.schema.table("redirection_host", (redirection_host) => { return knex.schema.table('redirection_host', function (redirection_host) {
redirection_host.integer("hsts_enabled").notNull().unsigned().defaultTo(0); redirection_host.integer('hsts_enabled').notNull().unsigned().defaultTo(0);
redirection_host.integer("hsts_subdomains").notNull().unsigned().defaultTo(0); redirection_host.integer('hsts_subdomains').notNull().unsigned().defaultTo(0);
}); });
}) })
.then(() => { .then(() => {
logger.info(`[${migrateName}] redirection_host Table altered`); logger.info('[' + migrate_name + '] redirection_host Table altered');
return knex.schema.table("dead_host", (dead_host) => { return knex.schema.table('dead_host', function (dead_host) {
dead_host.integer("hsts_enabled").notNull().unsigned().defaultTo(0); dead_host.integer('hsts_enabled').notNull().unsigned().defaultTo(0);
dead_host.integer("hsts_subdomains").notNull().unsigned().defaultTo(0); dead_host.integer('hsts_subdomains').notNull().unsigned().defaultTo(0);
}); });
}) })
.then(() => { .then(() => {
logger.info(`[${migrateName}] dead_host Table altered`); logger.info('[' + migrate_name + '] dead_host Table altered');
}); });
}; };
@@ -43,11 +42,10 @@ const up = (knex) => {
* Undo Migrate * Undo Migrate
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const down = (_knex) => { exports.down = function (knex, Promise) {
logger.warn(`[${migrateName}] You can't migrate down this one.`); logger.warn('[' + migrate_name + '] You can\'t migrate down this one.');
return Promise.resolve(true); return Promise.resolve(true);
}; };
export { up, down };

View File

@@ -1,6 +1,5 @@
import { migrate as logger } from "../logger.js"; const migrate_name = 'settings';
const logger = require('../logger').migrate;
const migrateName = "settings";
/** /**
* Migrate * Migrate
@@ -8,10 +7,11 @@ const migrateName = "settings";
* @see http://knexjs.org/#Schema * @see http://knexjs.org/#Schema
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const up = (knex) => { exports.up = function (knex/*, Promise*/) {
logger.info(`[${migrateName}] Migrating Up...`); logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema.createTable('setting', (table) => { return knex.schema.createTable('setting', (table) => {
table.string('id').notNull().primary(); table.string('id').notNull().primary();
@@ -21,7 +21,7 @@ const up = (knex) => {
table.json('meta').notNull(); table.json('meta').notNull();
}) })
.then(() => { .then(() => {
logger.info(`[${migrateName}] setting Table created`); logger.info('[' + migrate_name + '] setting Table created');
}); });
}; };
@@ -29,11 +29,10 @@ const up = (knex) => {
* Undo Migrate * Undo Migrate
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const down = (_knex) => { exports.down = function (knex, Promise) {
logger.warn(`[${migrateName}] You can't migrate down the initial data.`); logger.warn('[' + migrate_name + '] You can\'t migrate down the initial data.');
return Promise.resolve(true); return Promise.resolve(true);
}; };
export { up, down };

View File

@@ -1,6 +1,5 @@
import { migrate as logger } from "../logger.js"; const migrate_name = 'access_list_client';
const logger = require('../logger').migrate;
const migrateName = "access_list_client";
/** /**
* Migrate * Migrate
@@ -8,30 +7,32 @@ const migrateName = "access_list_client";
* @see http://knexjs.org/#Schema * @see http://knexjs.org/#Schema
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const up = (knex) => { exports.up = function (knex/*, Promise*/) {
logger.info(`[${migrateName}] Migrating Up...`);
return knex.schema logger.info('[' + migrate_name + '] Migrating Up...');
.createTable("access_list_client", (table) => {
return knex.schema.createTable('access_list_client', (table) => {
table.increments().primary(); table.increments().primary();
table.dateTime("created_on").notNull(); table.dateTime('created_on').notNull();
table.dateTime("modified_on").notNull(); table.dateTime('modified_on').notNull();
table.integer("access_list_id").notNull().unsigned(); table.integer('access_list_id').notNull().unsigned();
table.string("address").notNull(); table.string('address').notNull();
table.string("directive").notNull(); table.string('directive').notNull();
table.json("meta").notNull(); table.json('meta').notNull();
})
.then(() => {
logger.info(`[${migrateName}] access_list_client Table created`);
return knex.schema.table("access_list", (access_list) => { })
access_list.integer("satify_any").notNull().defaultTo(0); .then(function () {
logger.info('[' + migrate_name + '] access_list_client Table created');
return knex.schema.table('access_list', function (access_list) {
access_list.integer('satify_any').notNull().defaultTo(0);
}); });
}) })
.then(() => { .then(() => {
logger.info(`[${migrateName}] access_list Table altered`); logger.info('[' + migrate_name + '] access_list Table altered');
}); });
}; };
@@ -39,14 +40,14 @@ const up = (knex) => {
* Undo Migrate * Undo Migrate
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const down = (knex) => { exports.down = function (knex/*, Promise*/) {
logger.info(`[${migrateName}] Migrating Down...`); logger.info('[' + migrate_name + '] Migrating Down...');
return knex.schema.dropTable("access_list_client").then(() => { return knex.schema.dropTable('access_list_client')
logger.info(`[${migrateName}] access_list_client Table dropped`); .then(() => {
logger.info('[' + migrate_name + '] access_list_client Table dropped');
}); });
}; };
export { up, down };

View File

@@ -1,6 +1,5 @@
import { migrate as logger } from "../logger.js"; const migrate_name = 'access_list_client_fix';
const logger = require('../logger').migrate;
const migrateName = "access_list_client_fix";
/** /**
* Migrate * Migrate
@@ -8,17 +7,17 @@ const migrateName = "access_list_client_fix";
* @see http://knexjs.org/#Schema * @see http://knexjs.org/#Schema
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const up = (knex) => { exports.up = function (knex/*, Promise*/) {
logger.info(`[${migrateName}] Migrating Up...`); logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema return knex.schema.table('access_list', function (access_list) {
.table("access_list", (access_list) => { access_list.renameColumn('satify_any', 'satisfy_any');
access_list.renameColumn("satify_any", "satisfy_any");
}) })
.then(() => { .then(() => {
logger.info(`[${migrateName}] access_list Table altered`); logger.info('[' + migrate_name + '] access_list Table altered');
}); });
}; };
@@ -26,11 +25,10 @@ const up = (knex) => {
* Undo Migrate * Undo Migrate
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const down = (_knex) => { exports.down = function (knex, Promise) {
logger.warn(`[${migrateName}] You can't migrate down this one.`); logger.warn('[' + migrate_name + '] You can\'t migrate down this one.');
return Promise.resolve(true); return Promise.resolve(true);
}; };
export { up, down };

View File

@@ -1,6 +1,5 @@
import { migrate as logger } from "../logger.js"; const migrate_name = 'pass_auth';
const logger = require('../logger').migrate;
const migrateName = "pass_auth";
/** /**
* Migrate * Migrate
@@ -8,17 +7,18 @@ const migrateName = "pass_auth";
* @see http://knexjs.org/#Schema * @see http://knexjs.org/#Schema
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const up = (knex) => { exports.up = function (knex/*, Promise*/) {
logger.info(`[${migrateName}] Migrating Up...`);
return knex.schema logger.info('[' + migrate_name + '] Migrating Up...');
.table("access_list", (access_list) => {
access_list.integer("pass_auth").notNull().defaultTo(1); return knex.schema.table('access_list', function (access_list) {
access_list.integer('pass_auth').notNull().defaultTo(1);
}) })
.then(() => { .then(() => {
logger.info(`[${migrateName}] access_list Table altered`); logger.info('[' + migrate_name + '] access_list Table altered');
}); });
}; };
@@ -26,18 +26,16 @@ const up = (knex) => {
* Undo Migrate * Undo Migrate
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const down = (knex) => { exports.down = function (knex/*, Promise*/) {
logger.info(`[${migrateName}] Migrating Down...`); logger.info('[' + migrate_name + '] Migrating Down...');
return knex.schema return knex.schema.table('access_list', function (access_list) {
.table("access_list", (access_list) => { access_list.dropColumn('pass_auth');
access_list.dropColumn("pass_auth");
}) })
.then(() => { .then(() => {
logger.info(`[${migrateName}] access_list pass_auth Column dropped`); logger.info('[' + migrate_name + '] access_list pass_auth Column dropped');
}); });
}; };
export { up, down };

View File

@@ -1,6 +1,5 @@
import { migrate as logger } from "../logger.js"; const migrate_name = 'redirection_scheme';
const logger = require('../logger').migrate;
const migrateName = "redirection_scheme";
/** /**
* Migrate * Migrate
@@ -8,17 +7,18 @@ const migrateName = "redirection_scheme";
* @see http://knexjs.org/#Schema * @see http://knexjs.org/#Schema
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const up = (knex) => { exports.up = function (knex/*, Promise*/) {
logger.info(`[${migrateName}] Migrating Up...`);
return knex.schema logger.info('[' + migrate_name + '] Migrating Up...');
.table("redirection_host", (table) => {
table.string("forward_scheme").notNull().defaultTo("$scheme"); return knex.schema.table('redirection_host', (table) => {
table.string('forward_scheme').notNull().defaultTo('$scheme');
}) })
.then(() => { .then(function () {
logger.info(`[${migrateName}] redirection_host Table altered`); logger.info('[' + migrate_name + '] redirection_host Table altered');
}); });
}; };
@@ -26,18 +26,16 @@ const up = (knex) => {
* Undo Migrate * Undo Migrate
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const down = (knex) => { exports.down = function (knex/*, Promise*/) {
logger.info(`[${migrateName}] Migrating Down...`); logger.info('[' + migrate_name + '] Migrating Down...');
return knex.schema return knex.schema.table('redirection_host', (table) => {
.table("redirection_host", (table) => { table.dropColumn('forward_scheme');
table.dropColumn("forward_scheme");
}) })
.then(() => { .then(function () {
logger.info(`[${migrateName}] redirection_host Table altered`); logger.info('[' + migrate_name + '] redirection_host Table altered');
}); });
}; };
export { up, down };

View File

@@ -1,6 +1,5 @@
import { migrate as logger } from "../logger.js"; const migrate_name = 'redirection_status_code';
const logger = require('../logger').migrate;
const migrateName = "redirection_status_code";
/** /**
* Migrate * Migrate
@@ -8,17 +7,18 @@ const migrateName = "redirection_status_code";
* @see http://knexjs.org/#Schema * @see http://knexjs.org/#Schema
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const up = (knex) => { exports.up = function (knex/*, Promise*/) {
logger.info(`[${migrateName}] Migrating Up...`);
return knex.schema logger.info('[' + migrate_name + '] Migrating Up...');
.table("redirection_host", (table) => {
table.integer("forward_http_code").notNull().unsigned().defaultTo(302); return knex.schema.table('redirection_host', (table) => {
table.integer('forward_http_code').notNull().unsigned().defaultTo(302);
}) })
.then(() => { .then(function () {
logger.info(`[${migrateName}] redirection_host Table altered`); logger.info('[' + migrate_name + '] redirection_host Table altered');
}); });
}; };
@@ -26,18 +26,16 @@ const up = (knex) => {
* Undo Migrate * Undo Migrate
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const down = (knex) => { exports.down = function (knex/*, Promise*/) {
logger.info(`[${migrateName}] Migrating Down...`); logger.info('[' + migrate_name + '] Migrating Down...');
return knex.schema return knex.schema.table('redirection_host', (table) => {
.table("redirection_host", (table) => { table.dropColumn('forward_http_code');
table.dropColumn("forward_http_code");
}) })
.then(() => { .then(function () {
logger.info(`[${migrateName}] redirection_host Table altered`); logger.info('[' + migrate_name + '] redirection_host Table altered');
}); });
}; };
export { up, down };

View File

@@ -1,6 +1,5 @@
import { migrate as logger } from "../logger.js"; const migrate_name = 'stream_domain';
const logger = require('../logger').migrate;
const migrateName = "stream_domain";
/** /**
* Migrate * Migrate
@@ -8,17 +7,17 @@ const migrateName = "stream_domain";
* @see http://knexjs.org/#Schema * @see http://knexjs.org/#Schema
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const up = (knex) => { exports.up = function (knex/*, Promise*/) {
logger.info(`[${migrateName}] Migrating Up...`); logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema return knex.schema.table('stream', (table) => {
.table("stream", (table) => { table.renameColumn('forward_ip', 'forwarding_host');
table.renameColumn("forward_ip", "forwarding_host");
}) })
.then(() => { .then(function () {
logger.info(`[${migrateName}] stream Table altered`); logger.info('[' + migrate_name + '] stream Table altered');
}); });
}; };
@@ -26,18 +25,16 @@ const up = (knex) => {
* Undo Migrate * Undo Migrate
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const down = (knex) => { exports.down = function (knex/*, Promise*/) {
logger.info(`[${migrateName}] Migrating Down...`); logger.info('[' + migrate_name + '] Migrating Down...');
return knex.schema return knex.schema.table('stream', (table) => {
.table("stream", (table) => { table.renameColumn('forwarding_host', 'forward_ip');
table.renameColumn("forwarding_host", "forward_ip");
}) })
.then(() => { .then(function () {
logger.info(`[${migrateName}] stream Table altered`); logger.info('[' + migrate_name + '] stream Table altered');
}); });
}; };
export { up, down };

View File

@@ -1,19 +1,17 @@
import internalNginx from "../internal/nginx.js"; const migrate_name = 'stream_domain';
import { migrate as logger } from "../logger.js"; const logger = require('../logger').migrate;
const internalNginx = require('../internal/nginx');
const migrateName = "stream_domain";
async function regenerateDefaultHost(knex) { async function regenerateDefaultHost(knex) {
const row = await knex("setting").select("*").where("id", "default-site").first(); const row = await knex('setting').select('*').where('id', 'default-site').first();
if (!row) { if (!row) {
return Promise.resolve(); return Promise.resolve();
} }
return internalNginx return internalNginx.deleteConfig('default')
.deleteConfig("default")
.then(() => { .then(() => {
return internalNginx.generateConfig("default", row); return internalNginx.generateConfig('default', row);
}) })
.then(() => { .then(() => {
return internalNginx.test(); return internalNginx.test();
@@ -29,10 +27,11 @@ async function regenerateDefaultHost(knex) {
* @see http://knexjs.org/#Schema * @see http://knexjs.org/#Schema
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const up = (knex) => { exports.up = function (knex) {
logger.info(`[${migrateName}] Migrating Up...`); logger.info('[' + migrate_name + '] Migrating Up...');
return regenerateDefaultHost(knex); return regenerateDefaultHost(knex);
}; };
@@ -41,12 +40,11 @@ const up = (knex) => {
* Undo Migrate * Undo Migrate
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
const down = (knex) => { exports.down = function (knex) {
logger.info(`[${migrateName}] Migrating Down...`); logger.info('[' + migrate_name + '] Migrating Down...');
return regenerateDefaultHost(knex); return regenerateDefaultHost(knex);
}; };
export { up, down };

View File

@@ -1,6 +1,5 @@
import { migrate as logger } from "../logger.js"; const migrate_name = 'stream_ssl';
const logger = require('../logger').migrate;
const migrateName = "stream_ssl";
/** /**
* Migrate * Migrate
@@ -10,15 +9,14 @@ const migrateName = "stream_ssl";
* @param {Object} knex * @param {Object} knex
* @returns {Promise} * @returns {Promise}
*/ */
const up = (knex) => { exports.up = function (knex) {
logger.info(`[${migrateName}] Migrating Up...`); logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema return knex.schema.table('stream', (table) => {
.table("stream", (table) => { table.integer('certificate_id').notNull().unsigned().defaultTo(0);
table.integer("certificate_id").notNull().unsigned().defaultTo(0);
}) })
.then(() => { .then(function () {
logger.info(`[${migrateName}] stream Table altered`); logger.info('[' + migrate_name + '] stream Table altered');
}); });
}; };
@@ -28,16 +26,13 @@ const up = (knex) => {
* @param {Object} knex * @param {Object} knex
* @returns {Promise} * @returns {Promise}
*/ */
const down = (knex) => { exports.down = function (knex) {
logger.info(`[${migrateName}] Migrating Down...`); logger.info('[' + migrate_name + '] Migrating Down...');
return knex.schema return knex.schema.table('stream', (table) => {
.table("stream", (table) => { table.dropColumn('certificate_id');
table.dropColumn("certificate_id");
}) })
.then(() => { .then(function () {
logger.info(`[${migrateName}] stream Table altered`); logger.info('[' + migrate_name + '] stream Table altered');
}); });
}; };
export { up, down };

View File

@@ -1,18 +1,21 @@
// Objection Docs: // Objection Docs:
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
import { Model } from "objection"; const db = require('../db');
import db from "../db.js"; const helpers = require('../lib/helpers');
import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js"; const Model = require('objection').Model;
import AccessListAuth from "./access_list_auth.js"; const User = require('./user');
import AccessListClient from "./access_list_client.js"; const AccessListAuth = require('./access_list_auth');
import now from "./now_helper.js"; const AccessListClient = require('./access_list_client');
import ProxyHostModel from "./proxy_host.js"; const now = require('./now_helper');
import User from "./user.js";
Model.knex(db); Model.knex(db);
const boolFields = ["is_deleted", "satisfy_any", "pass_auth"]; const boolFields = [
'is_deleted',
'satisfy_any',
'pass_auth',
];
class AccessList extends Model { class AccessList extends Model {
$beforeInsert () { $beforeInsert () {
@@ -20,7 +23,7 @@ class AccessList extends Model {
this.modified_on = now(); this.modified_on = now();
// Default for meta // Default for meta
if (typeof this.meta === "undefined") { if (typeof this.meta === 'undefined') {
this.meta = {}; this.meta = {};
} }
} }
@@ -30,69 +33,71 @@ class AccessList extends Model {
} }
$parseDatabaseJson(json) { $parseDatabaseJson(json) {
const thisJson = super.$parseDatabaseJson(json); json = super.$parseDatabaseJson(json);
return convertIntFieldsToBool(thisJson, boolFields); return helpers.convertIntFieldsToBool(json, boolFields);
} }
$formatDatabaseJson(json) { $formatDatabaseJson(json) {
const thisJson = convertBoolFieldsToInt(json, boolFields); json = helpers.convertBoolFieldsToInt(json, boolFields);
return super.$formatDatabaseJson(thisJson); return super.$formatDatabaseJson(json);
} }
static get name () { static get name () {
return "AccessList"; return 'AccessList';
} }
static get tableName () { static get tableName () {
return "access_list"; return 'access_list';
} }
static get jsonAttributes () { static get jsonAttributes () {
return ["meta"]; return ['meta'];
} }
static get relationMappings () { static get relationMappings () {
const ProxyHost = require('./proxy_host');
return { return {
owner: { owner: {
relation: Model.HasOneRelation, relation: Model.HasOneRelation,
modelClass: User, modelClass: User,
join: { join: {
from: "access_list.owner_user_id", from: 'access_list.owner_user_id',
to: "user.id", to: 'user.id'
},
modify: (qb) => {
qb.where("user.is_deleted", 0);
}, },
modify: function (qb) {
qb.where('user.is_deleted', 0);
}
}, },
items: { items: {
relation: Model.HasManyRelation, relation: Model.HasManyRelation,
modelClass: AccessListAuth, modelClass: AccessListAuth,
join: { join: {
from: "access_list.id", from: 'access_list.id',
to: "access_list_auth.access_list_id", to: 'access_list_auth.access_list_id'
}, }
}, },
clients: { clients: {
relation: Model.HasManyRelation, relation: Model.HasManyRelation,
modelClass: AccessListClient, modelClass: AccessListClient,
join: { join: {
from: "access_list.id", from: 'access_list.id',
to: "access_list_client.access_list_id", to: 'access_list_client.access_list_id'
}, }
}, },
proxy_hosts: { proxy_hosts: {
relation: Model.HasManyRelation, relation: Model.HasManyRelation,
modelClass: ProxyHostModel, modelClass: ProxyHost,
join: { join: {
from: "access_list.id", from: 'access_list.id',
to: "proxy_host.access_list_id", to: 'proxy_host.access_list_id'
},
modify: (qb) => {
qb.where("proxy_host.is_deleted", 0);
},
}, },
modify: function (qb) {
qb.where('proxy_host.is_deleted', 0);
}
}
}; };
} }
} }
export default AccessList; module.exports = AccessList;

View File

@@ -1,10 +1,9 @@
// Objection Docs: // Objection Docs:
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
import { Model } from "objection"; const db = require('../db');
import db from "../db.js"; const Model = require('objection').Model;
import accessListModel from "./access_list.js"; const now = require('./now_helper');
import now from "./now_helper.js";
Model.knex(db); Model.knex(db);
@@ -14,7 +13,7 @@ class AccessListAuth extends Model {
this.modified_on = now(); this.modified_on = now();
// Default for meta // Default for meta
if (typeof this.meta === "undefined") { if (typeof this.meta === 'undefined') {
this.meta = {}; this.meta = {};
} }
} }
@@ -24,32 +23,32 @@ class AccessListAuth extends Model {
} }
static get name () { static get name () {
return "AccessListAuth"; return 'AccessListAuth';
} }
static get tableName () { static get tableName () {
return "access_list_auth"; return 'access_list_auth';
} }
static get jsonAttributes () { static get jsonAttributes () {
return ["meta"]; return ['meta'];
} }
static get relationMappings () { static get relationMappings () {
return { return {
access_list: { access_list: {
relation: Model.HasOneRelation, relation: Model.HasOneRelation,
modelClass: accessListModel, modelClass: require('./access_list'),
join: { join: {
from: "access_list_auth.access_list_id", from: 'access_list_auth.access_list_id',
to: "access_list.id", to: 'access_list.id'
},
modify: (qb) => {
qb.where("access_list.is_deleted", 0);
},
}, },
modify: function (qb) {
qb.where('access_list.is_deleted', 0);
}
}
}; };
} }
} }
export default AccessListAuth; module.exports = AccessListAuth;

View File

@@ -1,10 +1,9 @@
// Objection Docs: // Objection Docs:
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
import { Model } from "objection"; const db = require('../db');
import db from "../db.js"; const Model = require('objection').Model;
import accessListModel from "./access_list.js"; const now = require('./now_helper');
import now from "./now_helper.js";
Model.knex(db); Model.knex(db);
@@ -14,7 +13,7 @@ class AccessListClient extends Model {
this.modified_on = now(); this.modified_on = now();
// Default for meta // Default for meta
if (typeof this.meta === "undefined") { if (typeof this.meta === 'undefined') {
this.meta = {}; this.meta = {};
} }
} }
@@ -24,32 +23,32 @@ class AccessListClient extends Model {
} }
static get name () { static get name () {
return "AccessListClient"; return 'AccessListClient';
} }
static get tableName () { static get tableName () {
return "access_list_client"; return 'access_list_client';
} }
static get jsonAttributes () { static get jsonAttributes () {
return ["meta"]; return ['meta'];
} }
static get relationMappings () { static get relationMappings () {
return { return {
access_list: { access_list: {
relation: Model.HasOneRelation, relation: Model.HasOneRelation,
modelClass: accessListModel, modelClass: require('./access_list'),
join: { join: {
from: "access_list_client.access_list_id", from: 'access_list_client.access_list_id',
to: "access_list.id", to: 'access_list.id'
},
modify: (qb) => {
qb.where("access_list.is_deleted", 0);
},
}, },
modify: function (qb) {
qb.where('access_list.is_deleted', 0);
}
}
}; };
} }
} }
export default AccessListClient; module.exports = AccessListClient;

View File

@@ -1,10 +1,10 @@
// Objection Docs: // Objection Docs:
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
import { Model } from "objection"; const db = require('../db');
import db from "../db.js"; const Model = require('objection').Model;
import now from "./now_helper.js"; const User = require('./user');
import User from "./user.js"; const now = require('./now_helper');
Model.knex(db); Model.knex(db);
@@ -14,7 +14,7 @@ class AuditLog extends Model {
this.modified_on = now(); this.modified_on = now();
// Default for meta // Default for meta
if (typeof this.meta === "undefined") { if (typeof this.meta === 'undefined') {
this.meta = {}; this.meta = {};
} }
} }
@@ -24,15 +24,15 @@ class AuditLog extends Model {
} }
static get name () { static get name () {
return "AuditLog"; return 'AuditLog';
} }
static get tableName () { static get tableName () {
return "audit_log"; return 'audit_log';
} }
static get jsonAttributes () { static get jsonAttributes () {
return ["meta"]; return ['meta'];
} }
static get relationMappings () { static get relationMappings () {
@@ -41,12 +41,12 @@ class AuditLog extends Model {
relation: Model.HasOneRelation, relation: Model.HasOneRelation,
modelClass: User, modelClass: User,
join: { join: {
from: "audit_log.user_id", from: 'audit_log.user_id',
to: "user.id", to: 'user.id'
}, }
}, }
}; };
} }
} }
export default AuditLog; module.exports = AuditLog;

View File

@@ -1,21 +1,27 @@
// Objection Docs: // Objection Docs:
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
import bcrypt from "bcrypt"; const bcrypt = require('bcrypt');
import { Model } from "objection"; const db = require('../db');
import db from "../db.js"; const helpers = require('../lib/helpers');
import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js"; const Model = require('objection').Model;
import now from "./now_helper.js"; const User = require('./user');
import User from "./user.js"; const now = require('./now_helper');
Model.knex(db); Model.knex(db);
const boolFields = ["is_deleted"]; const boolFields = [
'is_deleted',
];
function encryptPassword () { function encryptPassword () {
if (this.type === "password" && this.secret) { /* jshint -W040 */
return bcrypt.hash(this.secret, 13).then((hash) => { let _this = this;
this.secret = hash;
if (_this.type === 'password' && _this.secret) {
return bcrypt.hash(_this.secret, 13)
.then(function (hash) {
_this.secret = hash;
}); });
} }
@@ -28,7 +34,7 @@ class Auth extends Model {
this.modified_on = now(); this.modified_on = now();
// Default for meta // Default for meta
if (typeof this.meta === "undefined") { if (typeof this.meta === 'undefined') {
this.meta = {}; this.meta = {};
} }
@@ -41,13 +47,13 @@ class Auth extends Model {
} }
$parseDatabaseJson(json) { $parseDatabaseJson(json) {
const thisJson = super.$parseDatabaseJson(json); json = super.$parseDatabaseJson(json);
return convertIntFieldsToBool(thisJson, boolFields); return helpers.convertIntFieldsToBool(json, boolFields);
} }
$formatDatabaseJson(json) { $formatDatabaseJson(json) {
const thisJson = convertBoolFieldsToInt(json, boolFields); json = helpers.convertBoolFieldsToInt(json, boolFields);
return super.$formatDatabaseJson(thisJson); return super.$formatDatabaseJson(json);
} }
/** /**
@@ -61,15 +67,15 @@ class Auth extends Model {
} }
static get name () { static get name () {
return "Auth"; return 'Auth';
} }
static get tableName () { static get tableName () {
return "auth"; return 'auth';
} }
static get jsonAttributes () { static get jsonAttributes () {
return ["meta"]; return ['meta'];
} }
static get relationMappings () { static get relationMappings () {
@@ -78,15 +84,15 @@ class Auth extends Model {
relation: Model.HasOneRelation, relation: Model.HasOneRelation,
modelClass: User, modelClass: User,
join: { join: {
from: "auth.user_id", from: 'auth.user_id',
to: "user.id", to: 'user.id'
}, },
filter: { filter: {
is_deleted: 0, is_deleted: 0
}, }
}, }
}; };
} }
} }
export default Auth; module.exports = Auth;

View File

@@ -1,18 +1,16 @@
// Objection Docs: // Objection Docs:
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
import { Model } from "objection"; const db = require('../db');
import db from "../db.js"; const helpers = require('../lib/helpers');
import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js"; const Model = require('objection').Model;
import deadHostModel from "./dead_host.js"; const now = require('./now_helper');
import now from "./now_helper.js";
import proxyHostModel from "./proxy_host.js";
import redirectionHostModel from "./redirection_host.js";
import userModel from "./user.js";
Model.knex(db); Model.knex(db);
const boolFields = ["is_deleted"]; const boolFields = [
'is_deleted',
];
class Certificate extends Model { class Certificate extends Model {
$beforeInsert () { $beforeInsert () {
@@ -20,17 +18,17 @@ class Certificate extends Model {
this.modified_on = now(); this.modified_on = now();
// Default for expires_on // Default for expires_on
if (typeof this.expires_on === "undefined") { if (typeof this.expires_on === 'undefined') {
this.expires_on = now(); this.expires_on = now();
} }
// Default for domain_names // Default for domain_names
if (typeof this.domain_names === "undefined") { if (typeof this.domain_names === 'undefined') {
this.domain_names = []; this.domain_names = [];
} }
// Default for meta // Default for meta
if (typeof this.meta === "undefined") { if (typeof this.meta === 'undefined') {
this.meta = {}; this.meta = {};
} }
@@ -41,81 +39,86 @@ class Certificate extends Model {
this.modified_on = now(); this.modified_on = now();
// Sort domain_names // Sort domain_names
if (typeof this.domain_names !== "undefined") { if (typeof this.domain_names !== 'undefined') {
this.domain_names.sort(); this.domain_names.sort();
} }
} }
$parseDatabaseJson(json) { $parseDatabaseJson(json) {
const thisJson = super.$parseDatabaseJson(json); json = super.$parseDatabaseJson(json);
return convertIntFieldsToBool(thisJson, boolFields); return helpers.convertIntFieldsToBool(json, boolFields);
} }
$formatDatabaseJson(json) { $formatDatabaseJson(json) {
const thisJson = convertBoolFieldsToInt(json, boolFields); json = helpers.convertBoolFieldsToInt(json, boolFields);
return super.$formatDatabaseJson(thisJson); return super.$formatDatabaseJson(json);
} }
static get name () { static get name () {
return "Certificate"; return 'Certificate';
} }
static get tableName () { static get tableName () {
return "certificate"; return 'certificate';
} }
static get jsonAttributes () { static get jsonAttributes () {
return ["domain_names", "meta"]; return ['domain_names', 'meta'];
} }
static get relationMappings () { static get relationMappings () {
const ProxyHost = require('./proxy_host');
const DeadHost = require('./dead_host');
const User = require('./user');
const RedirectionHost = require('./redirection_host');
return { return {
owner: { owner: {
relation: Model.HasOneRelation, relation: Model.HasOneRelation,
modelClass: userModel, modelClass: User,
join: { join: {
from: "certificate.owner_user_id", from: 'certificate.owner_user_id',
to: "user.id", to: 'user.id'
},
modify: (qb) => {
qb.where("user.is_deleted", 0);
}, },
modify: function (qb) {
qb.where('user.is_deleted', 0);
}
}, },
proxy_hosts: { proxy_hosts: {
relation: Model.HasManyRelation, relation: Model.HasManyRelation,
modelClass: proxyHostModel, modelClass: ProxyHost,
join: { join: {
from: "certificate.id", from: 'certificate.id',
to: "proxy_host.certificate_id", to: 'proxy_host.certificate_id'
},
modify: (qb) => {
qb.where("proxy_host.is_deleted", 0);
}, },
modify: function (qb) {
qb.where('proxy_host.is_deleted', 0);
}
}, },
dead_hosts: { dead_hosts: {
relation: Model.HasManyRelation, relation: Model.HasManyRelation,
modelClass: deadHostModel, modelClass: DeadHost,
join: { join: {
from: "certificate.id", from: 'certificate.id',
to: "dead_host.certificate_id", to: 'dead_host.certificate_id'
},
modify: (qb) => {
qb.where("dead_host.is_deleted", 0);
}, },
modify: function (qb) {
qb.where('dead_host.is_deleted', 0);
}
}, },
redirection_hosts: { redirection_hosts: {
relation: Model.HasManyRelation, relation: Model.HasManyRelation,
modelClass: redirectionHostModel, modelClass: RedirectionHost,
join: { join: {
from: "certificate.id", from: 'certificate.id',
to: "redirection_host.certificate_id", to: 'redirection_host.certificate_id'
},
modify: (qb) => {
qb.where("redirection_host.is_deleted", 0);
},
}, },
modify: function (qb) {
qb.where('redirection_host.is_deleted', 0);
}
}
}; };
} }
} }
export default Certificate; module.exports = Certificate;

View File

@@ -1,16 +1,23 @@
// Objection Docs: // Objection Docs:
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
import { Model } from "objection"; const db = require('../db');
import db from "../db.js"; const helpers = require('../lib/helpers');
import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js"; const Model = require('objection').Model;
import Certificate from "./certificate.js"; const User = require('./user');
import now from "./now_helper.js"; const Certificate = require('./certificate');
import User from "./user.js"; const now = require('./now_helper');
Model.knex(db); Model.knex(db);
const boolFields = ["is_deleted", "ssl_forced", "http2_support", "enabled", "hsts_enabled", "hsts_subdomains"]; const boolFields = [
'is_deleted',
'ssl_forced',
'http2_support',
'enabled',
'hsts_enabled',
'hsts_subdomains',
];
class DeadHost extends Model { class DeadHost extends Model {
$beforeInsert () { $beforeInsert () {
@@ -18,12 +25,12 @@ class DeadHost extends Model {
this.modified_on = now(); this.modified_on = now();
// Default for domain_names // Default for domain_names
if (typeof this.domain_names === "undefined") { if (typeof this.domain_names === 'undefined') {
this.domain_names = []; this.domain_names = [];
} }
// Default for meta // Default for meta
if (typeof this.meta === "undefined") { if (typeof this.meta === 'undefined') {
this.meta = {}; this.meta = {};
} }
@@ -34,31 +41,31 @@ class DeadHost extends Model {
this.modified_on = now(); this.modified_on = now();
// Sort domain_names // Sort domain_names
if (typeof this.domain_names !== "undefined") { if (typeof this.domain_names !== 'undefined') {
this.domain_names.sort(); this.domain_names.sort();
} }
} }
$parseDatabaseJson(json) { $parseDatabaseJson(json) {
const thisJson = super.$parseDatabaseJson(json); json = super.$parseDatabaseJson(json);
return convertIntFieldsToBool(thisJson, boolFields); return helpers.convertIntFieldsToBool(json, boolFields);
} }
$formatDatabaseJson(json) { $formatDatabaseJson(json) {
const thisJson = convertBoolFieldsToInt(json, boolFields); json = helpers.convertBoolFieldsToInt(json, boolFields);
return super.$formatDatabaseJson(thisJson); return super.$formatDatabaseJson(json);
} }
static get name () { static get name () {
return "DeadHost"; return 'DeadHost';
} }
static get tableName () { static get tableName () {
return "dead_host"; return 'dead_host';
} }
static get jsonAttributes () { static get jsonAttributes () {
return ["domain_names", "meta"]; return ['domain_names', 'meta'];
} }
static get relationMappings () { static get relationMappings () {
@@ -67,26 +74,26 @@ class DeadHost extends Model {
relation: Model.HasOneRelation, relation: Model.HasOneRelation,
modelClass: User, modelClass: User,
join: { join: {
from: "dead_host.owner_user_id", from: 'dead_host.owner_user_id',
to: "user.id", to: 'user.id'
},
modify: (qb) => {
qb.where("user.is_deleted", 0);
}, },
modify: function (qb) {
qb.where('user.is_deleted', 0);
}
}, },
certificate: { certificate: {
relation: Model.HasOneRelation, relation: Model.HasOneRelation,
modelClass: Certificate, modelClass: Certificate,
join: { join: {
from: "dead_host.certificate_id", from: 'dead_host.certificate_id',
to: "certificate.id", to: 'certificate.id'
},
modify: (qb) => {
qb.where("certificate.is_deleted", 0);
},
}, },
modify: function (qb) {
qb.where('certificate.is_deleted', 0);
}
}
}; };
} }
} }
export default DeadHost; module.exports = DeadHost;

View File

@@ -1,12 +1,13 @@
import { Model } from "objection"; const db = require('../db');
import db from "../db.js"; const config = require('../lib/config');
import { isSqlite } from "../lib/config.js"; const Model = require('objection').Model;
Model.knex(db); Model.knex(db);
export default () => { module.exports = function () {
if (isSqlite()) { if (config.isSqlite()) {
// eslint-disable-next-line
return Model.raw("datetime('now','localtime')"); return Model.raw("datetime('now','localtime')");
} }
return Model.raw("NOW()"); return Model.raw('NOW()');
}; };

View File

@@ -1,26 +1,26 @@
// Objection Docs: // Objection Docs:
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
import { Model } from "objection"; const db = require('../db');
import db from "../db.js"; const helpers = require('../lib/helpers');
import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js"; const Model = require('objection').Model;
import AccessList from "./access_list.js"; const User = require('./user');
import Certificate from "./certificate.js"; const AccessList = require('./access_list');
import now from "./now_helper.js"; const Certificate = require('./certificate');
import User from "./user.js"; const now = require('./now_helper');
Model.knex(db); Model.knex(db);
const boolFields = [ const boolFields = [
"is_deleted", 'is_deleted',
"ssl_forced", 'ssl_forced',
"caching_enabled", 'caching_enabled',
"block_exploits", 'block_exploits',
"allow_websocket_upgrade", 'allow_websocket_upgrade',
"http2_support", 'http2_support',
"enabled", 'enabled',
"hsts_enabled", 'hsts_enabled',
"hsts_subdomains", 'hsts_subdomains',
]; ];
class ProxyHost extends Model { class ProxyHost extends Model {
@@ -29,12 +29,12 @@ class ProxyHost extends Model {
this.modified_on = now(); this.modified_on = now();
// Default for domain_names // Default for domain_names
if (typeof this.domain_names === "undefined") { if (typeof this.domain_names === 'undefined') {
this.domain_names = []; this.domain_names = [];
} }
// Default for meta // Default for meta
if (typeof this.meta === "undefined") { if (typeof this.meta === 'undefined') {
this.meta = {}; this.meta = {};
} }
@@ -45,31 +45,31 @@ class ProxyHost extends Model {
this.modified_on = now(); this.modified_on = now();
// Sort domain_names // Sort domain_names
if (typeof this.domain_names !== "undefined") { if (typeof this.domain_names !== 'undefined') {
this.domain_names.sort(); this.domain_names.sort();
} }
} }
$parseDatabaseJson(json) { $parseDatabaseJson(json) {
const thisJson = super.$parseDatabaseJson(json); json = super.$parseDatabaseJson(json);
return convertIntFieldsToBool(thisJson, boolFields); return helpers.convertIntFieldsToBool(json, boolFields);
} }
$formatDatabaseJson(json) { $formatDatabaseJson(json) {
const thisJson = convertBoolFieldsToInt(json, boolFields); json = helpers.convertBoolFieldsToInt(json, boolFields);
return super.$formatDatabaseJson(thisJson); return super.$formatDatabaseJson(json);
} }
static get name () { static get name () {
return "ProxyHost"; return 'ProxyHost';
} }
static get tableName () { static get tableName () {
return "proxy_host"; return 'proxy_host';
} }
static get jsonAttributes () { static get jsonAttributes () {
return ["domain_names", "meta", "locations"]; return ['domain_names', 'meta', 'locations'];
} }
static get relationMappings () { static get relationMappings () {
@@ -78,37 +78,37 @@ class ProxyHost extends Model {
relation: Model.HasOneRelation, relation: Model.HasOneRelation,
modelClass: User, modelClass: User,
join: { join: {
from: "proxy_host.owner_user_id", from: 'proxy_host.owner_user_id',
to: "user.id", to: 'user.id'
},
modify: (qb) => {
qb.where("user.is_deleted", 0);
}, },
modify: function (qb) {
qb.where('user.is_deleted', 0);
}
}, },
access_list: { access_list: {
relation: Model.HasOneRelation, relation: Model.HasOneRelation,
modelClass: AccessList, modelClass: AccessList,
join: { join: {
from: "proxy_host.access_list_id", from: 'proxy_host.access_list_id',
to: "access_list.id", to: 'access_list.id'
},
modify: (qb) => {
qb.where("access_list.is_deleted", 0);
}, },
modify: function (qb) {
qb.where('access_list.is_deleted', 0);
}
}, },
certificate: { certificate: {
relation: Model.HasOneRelation, relation: Model.HasOneRelation,
modelClass: Certificate, modelClass: Certificate,
join: { join: {
from: "proxy_host.certificate_id", from: 'proxy_host.certificate_id',
to: "certificate.id", to: 'certificate.id'
},
modify: (qb) => {
qb.where("certificate.is_deleted", 0);
},
}, },
modify: function (qb) {
qb.where('certificate.is_deleted', 0);
}
}
}; };
} }
} }
export default ProxyHost; module.exports = ProxyHost;

View File

@@ -1,24 +1,25 @@
// Objection Docs: // Objection Docs:
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
import { Model } from "objection"; const db = require('../db');
import db from "../db.js"; const helpers = require('../lib/helpers');
import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js"; const Model = require('objection').Model;
import Certificate from "./certificate.js"; const User = require('./user');
import now from "./now_helper.js"; const Certificate = require('./certificate');
import User from "./user.js"; const now = require('./now_helper');
Model.knex(db); Model.knex(db);
const boolFields = [ const boolFields = [
"is_deleted", 'is_deleted',
"enabled", 'enabled',
"preserve_path", 'preserve_path',
"ssl_forced", 'ssl_forced',
"block_exploits", 'block_exploits',
"hsts_enabled", 'hsts_enabled',
"hsts_subdomains", 'hsts_subdomains',
"http2_support", 'http2_support',
]; ];
class RedirectionHost extends Model { class RedirectionHost extends Model {
@@ -27,12 +28,12 @@ class RedirectionHost extends Model {
this.modified_on = now(); this.modified_on = now();
// Default for domain_names // Default for domain_names
if (typeof this.domain_names === "undefined") { if (typeof this.domain_names === 'undefined') {
this.domain_names = []; this.domain_names = [];
} }
// Default for meta // Default for meta
if (typeof this.meta === "undefined") { if (typeof this.meta === 'undefined') {
this.meta = {}; this.meta = {};
} }
@@ -43,31 +44,31 @@ class RedirectionHost extends Model {
this.modified_on = now(); this.modified_on = now();
// Sort domain_names // Sort domain_names
if (typeof this.domain_names !== "undefined") { if (typeof this.domain_names !== 'undefined') {
this.domain_names.sort(); this.domain_names.sort();
} }
} }
$parseDatabaseJson(json) { $parseDatabaseJson(json) {
const thisJson = super.$parseDatabaseJson(json); json = super.$parseDatabaseJson(json);
return convertIntFieldsToBool(thisJson, boolFields); return helpers.convertIntFieldsToBool(json, boolFields);
} }
$formatDatabaseJson(json) { $formatDatabaseJson(json) {
const thisJson = convertBoolFieldsToInt(json, boolFields); json = helpers.convertBoolFieldsToInt(json, boolFields);
return super.$formatDatabaseJson(thisJson); return super.$formatDatabaseJson(json);
} }
static get name () { static get name () {
return "RedirectionHost"; return 'RedirectionHost';
} }
static get tableName () { static get tableName () {
return "redirection_host"; return 'redirection_host';
} }
static get jsonAttributes () { static get jsonAttributes () {
return ["domain_names", "meta"]; return ['domain_names', 'meta'];
} }
static get relationMappings () { static get relationMappings () {
@@ -76,26 +77,26 @@ class RedirectionHost extends Model {
relation: Model.HasOneRelation, relation: Model.HasOneRelation,
modelClass: User, modelClass: User,
join: { join: {
from: "redirection_host.owner_user_id", from: 'redirection_host.owner_user_id',
to: "user.id", to: 'user.id'
},
modify: (qb) => {
qb.where("user.is_deleted", 0);
}, },
modify: function (qb) {
qb.where('user.is_deleted', 0);
}
}, },
certificate: { certificate: {
relation: Model.HasOneRelation, relation: Model.HasOneRelation,
modelClass: Certificate, modelClass: Certificate,
join: { join: {
from: "redirection_host.certificate_id", from: 'redirection_host.certificate_id',
to: "certificate.id", to: 'certificate.id'
},
modify: (qb) => {
qb.where("certificate.is_deleted", 0);
},
}, },
modify: function (qb) {
qb.where('certificate.is_deleted', 0);
}
}
}; };
} }
} }
export default RedirectionHost; module.exports = RedirectionHost;

View File

@@ -1,8 +1,8 @@
// Objection Docs: // Objection Docs:
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
import { Model } from "objection"; const db = require('../db');
import db from "../db.js"; const Model = require('objection').Model;
Model.knex(db); Model.knex(db);
@@ -27,4 +27,4 @@ class Setting extends Model {
} }
} }
export default Setting; module.exports = Setting;

View File

@@ -1,13 +1,18 @@
import { Model } from "objection"; const Model = require('objection').Model;
import db from "../db.js"; const db = require('../db');
import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js"; const helpers = require('../lib/helpers');
import Certificate from "./certificate.js"; const User = require('./user');
import now from "./now_helper.js"; const Certificate = require('./certificate');
import User from "./user.js"; const now = require('./now_helper');
Model.knex(db); Model.knex(db);
const boolFields = ["is_deleted", "enabled", "tcp_forwarding", "udp_forwarding"]; const boolFields = [
'is_deleted',
'enabled',
'tcp_forwarding',
'udp_forwarding',
];
class Stream extends Model { class Stream extends Model {
$beforeInsert () { $beforeInsert () {
@@ -15,7 +20,7 @@ class Stream extends Model {
this.modified_on = now(); this.modified_on = now();
// Default for meta // Default for meta
if (typeof this.meta === "undefined") { if (typeof this.meta === 'undefined') {
this.meta = {}; this.meta = {};
} }
} }
@@ -25,25 +30,25 @@ class Stream extends Model {
} }
$parseDatabaseJson(json) { $parseDatabaseJson(json) {
const thisJson = super.$parseDatabaseJson(json); json = super.$parseDatabaseJson(json);
return convertIntFieldsToBool(thisJson, boolFields); return helpers.convertIntFieldsToBool(json, boolFields);
} }
$formatDatabaseJson(json) { $formatDatabaseJson(json) {
const thisJson = convertBoolFieldsToInt(json, boolFields); json = helpers.convertBoolFieldsToInt(json, boolFields);
return super.$formatDatabaseJson(thisJson); return super.$formatDatabaseJson(json);
} }
static get name () { static get name () {
return "Stream"; return 'Stream';
} }
static get tableName () { static get tableName () {
return "stream"; return 'stream';
} }
static get jsonAttributes () { static get jsonAttributes () {
return ["meta"]; return ['meta'];
} }
static get relationMappings () { static get relationMappings () {
@@ -52,26 +57,26 @@ class Stream extends Model {
relation: Model.HasOneRelation, relation: Model.HasOneRelation,
modelClass: User, modelClass: User,
join: { join: {
from: "stream.owner_user_id", from: 'stream.owner_user_id',
to: "user.id", to: 'user.id'
},
modify: (qb) => {
qb.where("user.is_deleted", 0);
}, },
modify: function (qb) {
qb.where('user.is_deleted', 0);
}
}, },
certificate: { certificate: {
relation: Model.HasOneRelation, relation: Model.HasOneRelation,
modelClass: Certificate, modelClass: Certificate,
join: { join: {
from: "stream.certificate_id", from: 'stream.certificate_id',
to: "certificate.id", to: 'certificate.id'
},
modify: (qb) => {
qb.where("certificate.is_deleted", 0);
},
}, },
modify: function (qb) {
qb.where('certificate.is_deleted', 0);
}
}
}; };
} }
} }
export default Stream; module.exports = Stream;

View File

@@ -3,17 +3,17 @@
and then has abilities after that. and then has abilities after that.
*/ */
import crypto from "node:crypto"; const _ = require('lodash');
import jwt from "jsonwebtoken"; const jwt = require('jsonwebtoken');
import _ from "lodash"; const crypto = require('crypto');
import { getPrivateKey, getPublicKey } from "../lib/config.js"; const config = require('../lib/config');
import errs from "../lib/error.js"; const error = require('../lib/error');
import { global as logger } from "../logger.js"; const logger = require('../logger').global;
const ALGO = 'RS256';
const ALGO = "RS256"; module.exports = function () {
export default () => { let token_data = {};
let tokenData = {};
const self = { const self = {
/** /**
@@ -21,26 +21,28 @@ export default () => {
* @returns {Promise} * @returns {Promise}
*/ */
create: (payload) => { create: (payload) => {
if (!getPrivateKey()) { if (!config.getPrivateKey()) {
logger.error("Private key is empty!"); logger.error('Private key is empty!');
} }
// sign with RSA SHA256 // sign with RSA SHA256
const options = { const options = {
algorithm: ALGO, algorithm: ALGO,
expiresIn: payload.expiresIn || "1d", expiresIn: payload.expiresIn || '1d'
}; };
payload.jti = crypto.randomBytes(12).toString("base64").substring(-8); payload.jti = crypto.randomBytes(12)
.toString('base64')
.substring(-8);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
jwt.sign(payload, getPrivateKey(), options, (err, token) => { jwt.sign(payload, config.getPrivateKey(), options, (err, token) => {
if (err) { if (err) {
reject(err); reject(err);
} else { } else {
tokenData = payload; token_data = payload;
resolve({ resolve({
token: token, token: token,
payload: payload, payload: payload
}); });
} }
}); });
@@ -51,47 +53,42 @@ export default () => {
* @param {String} token * @param {String} token
* @returns {Promise} * @returns {Promise}
*/ */
load: (token) => { load: function (token) {
if (!getPublicKey()) { if (!config.getPublicKey()) {
logger.error("Public key is empty!"); logger.error('Public key is empty!');
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
if (!token || token === null || token === "null") { if (!token || token === null || token === 'null') {
reject(new errs.AuthError("Empty token")); reject(new error.AuthError('Empty token'));
} else { } else {
jwt.verify( jwt.verify(token, config.getPublicKey(), {ignoreExpiration: false, algorithms: [ALGO]}, (err, result) => {
token,
getPublicKey(),
{ ignoreExpiration: false, algorithms: [ALGO] },
(err, result) => {
if (err) { if (err) {
if (err.name === "TokenExpiredError") {
reject(new errs.AuthError("Token has expired", err)); if (err.name === 'TokenExpiredError') {
reject(new error.AuthError('Token has expired', err));
} else { } else {
reject(err); reject(err);
} }
} else { } else {
tokenData = result; token_data = result;
// Hack: some tokens out in the wild have a scope of 'all' instead of 'user'. // Hack: some tokens out in the wild have a scope of 'all' instead of 'user'.
// For 30 days at least, we need to replace 'all' with user. // For 30 days at least, we need to replace 'all' with user.
if ( if ((typeof token_data.scope !== 'undefined' && _.indexOf(token_data.scope, 'all') !== -1)) {
typeof tokenData.scope !== "undefined" && token_data.scope = ['user'];
_.indexOf(tokenData.scope, "all") !== -1
) {
tokenData.scope = ["user"];
} }
resolve(tokenData); resolve(token_data);
} }
}, });
);
} }
} catch (err) { } catch (err) {
reject(err); reject(err);
} }
}); });
}, },
/** /**
@@ -100,15 +97,17 @@ export default () => {
* @param {String} scope * @param {String} scope
* @returns {Boolean} * @returns {Boolean}
*/ */
hasScope: (scope) => typeof tokenData.scope !== "undefined" && _.indexOf(tokenData.scope, scope) !== -1, hasScope: function (scope) {
return typeof token_data.scope !== 'undefined' && _.indexOf(token_data.scope, scope) !== -1;
},
/** /**
* @param {String} key * @param {String} key
* @return {*} * @return {*}
*/ */
get: (key) => { get: function (key) {
if (typeof tokenData[key] !== "undefined") { if (typeof token_data[key] !== 'undefined') {
return tokenData[key]; return token_data[key];
} }
return null; return null;
@@ -118,22 +117,22 @@ export default () => {
* @param {String} key * @param {String} key
* @param {*} value * @param {*} value
*/ */
set: (key, value) => { set: function (key, value) {
tokenData[key] = value; token_data[key] = value;
}, },
/** /**
* @param [defaultValue] * @param [default_value]
* @returns {Integer} * @returns {Integer}
*/ */
getUserId: (defaultValue) => { getUserId: (default_value) => {
const attrs = self.get("attrs"); const attrs = self.get('attrs');
if (attrs?.id) { if (attrs && typeof attrs.id !== 'undefined' && attrs.id) {
return attrs.id; return attrs.id;
} }
return defaultValue || 0; return default_value || 0;
}, }
}; };
return self; return self;

View File

@@ -1,15 +1,18 @@
// Objection Docs: // Objection Docs:
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
import { Model } from "objection"; const db = require('../db');
import db from "../db.js"; const helpers = require('../lib/helpers');
import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js"; const Model = require('objection').Model;
import now from "./now_helper.js"; const UserPermission = require('./user_permission');
import UserPermission from "./user_permission.js"; const now = require('./now_helper');
Model.knex(db); Model.knex(db);
const boolFields = ["is_deleted", "is_disabled"]; const boolFields = [
'is_deleted',
'is_disabled',
];
class User extends Model { class User extends Model {
$beforeInsert () { $beforeInsert () {
@@ -17,7 +20,7 @@ class User extends Model {
this.modified_on = now(); this.modified_on = now();
// Default for roles // Default for roles
if (typeof this.roles === "undefined") { if (typeof this.roles === 'undefined') {
this.roles = []; this.roles = [];
} }
} }
@@ -27,25 +30,25 @@ class User extends Model {
} }
$parseDatabaseJson(json) { $parseDatabaseJson(json) {
const thisJson = super.$parseDatabaseJson(json); json = super.$parseDatabaseJson(json);
return convertIntFieldsToBool(thisJson, boolFields); return helpers.convertIntFieldsToBool(json, boolFields);
} }
$formatDatabaseJson(json) { $formatDatabaseJson(json) {
const thisJson = convertBoolFieldsToInt(json, boolFields); json = helpers.convertBoolFieldsToInt(json, boolFields);
return super.$formatDatabaseJson(thisJson); return super.$formatDatabaseJson(json);
} }
static get name () { static get name () {
return "User"; return 'User';
} }
static get tableName () { static get tableName () {
return "user"; return 'user';
} }
static get jsonAttributes () { static get jsonAttributes () {
return ["roles"]; return ['roles'];
} }
static get relationMappings () { static get relationMappings () {
@@ -54,12 +57,13 @@ class User extends Model {
relation: Model.HasOneRelation, relation: Model.HasOneRelation,
modelClass: UserPermission, modelClass: UserPermission,
join: { join: {
from: "user.id", from: 'user.id',
to: "user_permission.user_id", to: 'user_permission.user_id'
}, }
}, }
}; };
} }
} }
export default User; module.exports = User;

View File

@@ -1,9 +1,9 @@
// Objection Docs: // Objection Docs:
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
import { Model } from "objection"; const db = require('../db');
import db from "../db.js"; const Model = require('objection').Model;
import now from "./now_helper.js"; const now = require('./now_helper');
Model.knex(db); Model.knex(db);
@@ -26,4 +26,4 @@ class UserPermission extends Model {
} }
} }
export default UserPermission; module.exports = UserPermission;

View File

@@ -3,5 +3,5 @@
"ignore": [ "ignore": [
"data" "data"
], ],
"ext": "js json ejs cjs" "ext": "js json ejs"
} }

View File

@@ -1,16 +1,8 @@
{ {
"name": "nginx-proxy-manager", "name": "nginx-proxy-manager",
"version": "2.0.0", "version": "0.0.0",
"description": "A beautiful interface for creating Nginx endpoints", "description": "A beautiful interface for creating Nginx endpoints",
"author": "Jamie Curnow <jc@jc21.com>",
"license": "MIT",
"main": "index.js", "main": "index.js",
"type": "module",
"scripts": {
"lint": "biome lint",
"prettier": "biome format --write .",
"validate-schema": "node validate-schema.js"
},
"dependencies": { "dependencies": {
"@apidevtools/json-schema-ref-parser": "^11.7.0", "@apidevtools/json-schema-ref-parser": "^11.7.0",
"ajv": "^8.17.1", "ajv": "^8.17.1",
@@ -36,14 +28,21 @@
"sqlite3": "5.1.6", "sqlite3": "5.1.6",
"temp-write": "^4.0.0" "temp-write": "^4.0.0"
}, },
"devDependencies": {
"@apidevtools/swagger-parser": "^10.1.0",
"@biomejs/biome": "^2.2.4",
"chalk": "4.1.2",
"nodemon": "^2.0.2"
},
"signale": { "signale": {
"displayDate": true, "displayDate": true,
"displayTimestamp": true "displayTimestamp": true
},
"author": "Jamie Curnow <jc@jc21.com>",
"license": "MIT",
"devDependencies": {
"@apidevtools/swagger-parser": "^10.1.0",
"chalk": "4.1.2",
"eslint": "^8.36.0",
"eslint-plugin-align-assignments": "^1.1.2",
"nodemon": "^2.0.2",
"prettier": "^2.0.4"
},
"scripts": {
"validate-schema": "node validate-schema.js"
} }
} }

View File

@@ -1,20 +1,19 @@
import express from "express"; const express = require('express');
import internalAuditLog from "../internal/audit-log.js"; const validator = require('../lib/validator');
import jwtdecode from "../lib/express/jwt-decode.js"; const jwtdecode = require('../lib/express/jwt-decode');
import validator from "../lib/validator/index.js"; const internalAuditLog = require('../internal/audit-log');
import { express as logger } from "../logger.js";
const router = express.Router({ let router = express.Router({
caseSensitive: true, caseSensitive: true,
strict: true, strict: true,
mergeParams: true, mergeParams: true
}); });
/** /**
* /api/audit-log * /api/audit-log
*/ */
router router
.route("/") .route('/')
.options((_, res) => { .options((_, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
@@ -25,31 +24,29 @@ router
* *
* Retrieve all logs * Retrieve all logs
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator({
const data = await validator(
{
additionalProperties: false, additionalProperties: false,
properties: { properties: {
expand: { expand: {
$ref: "common#/properties/expand", $ref: 'common#/properties/expand'
}, },
query: { query: {
$ref: "common#/properties/query", $ref: 'common#/properties/query'
},
},
},
{
expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null,
query: typeof req.query.query === "string" ? req.query.query : null,
},
);
const rows = await internalAuditLog.getAll(res.locals.access, data.expand, data.query);
res.status(200).send(rows);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
} }
}
}, {
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null),
query: (typeof req.query.query === 'string' ? req.query.query : null)
})
.then((data) => {
return internalAuditLog.getAll(res.locals.access, data.expand, data.query);
})
.then((rows) => {
res.status(200)
.send(rows);
})
.catch(next);
}); });
export default router; module.exports = router;

View File

@@ -1,66 +1,51 @@
import express from "express"; const express = require('express');
import errs from "../lib/error.js"; const pjson = require('../package.json');
import pjson from "../package.json" with { type: "json" }; const error = require('../lib/error');
import { isSetup } from "../setup.js";
import auditLogRoutes from "./audit-log.js";
import accessListsRoutes from "./nginx/access_lists.js";
import certificatesHostsRoutes from "./nginx/certificates.js";
import deadHostsRoutes from "./nginx/dead_hosts.js";
import proxyHostsRoutes from "./nginx/proxy_hosts.js";
import redirectionHostsRoutes from "./nginx/redirection_hosts.js";
import streamsRoutes from "./nginx/streams.js";
import reportsRoutes from "./reports.js";
import schemaRoutes from "./schema.js";
import settingsRoutes from "./settings.js";
import tokensRoutes from "./tokens.js";
import usersRoutes from "./users.js";
const router = express.Router({ let router = express.Router({
caseSensitive: true, caseSensitive: true,
strict: true, strict: true,
mergeParams: true, mergeParams: true
}); });
/** /**
* Health Check * Health Check
* GET /api * GET /api
*/ */
router.get("/", async (_, res /*, next*/) => { router.get('/', (req, res/*, next*/) => {
const version = pjson.version.split("-").shift().split("."); let version = pjson.version.split('-').shift().split('.');
const setup = await isSetup();
res.status(200).send({ res.status(200).send({
status: "OK", status: 'OK',
setup,
version: { version: {
major: Number.parseInt(version.shift(), 10), major: parseInt(version.shift(), 10),
minor: Number.parseInt(version.shift(), 10), minor: parseInt(version.shift(), 10),
revision: Number.parseInt(version.shift(), 10), revision: parseInt(version.shift(), 10)
}, }
}); });
}); });
router.use("/schema", schemaRoutes); router.use('/schema', require('./schema'));
router.use("/tokens", tokensRoutes); router.use('/tokens', require('./tokens'));
router.use("/users", usersRoutes); router.use('/users', require('./users'));
router.use("/audit-log", auditLogRoutes); router.use('/audit-log', require('./audit-log'));
router.use("/reports", reportsRoutes); router.use('/reports', require('./reports'));
router.use("/settings", settingsRoutes); router.use('/settings', require('./settings'));
router.use("/nginx/proxy-hosts", proxyHostsRoutes); router.use('/nginx/proxy-hosts', require('./nginx/proxy_hosts'));
router.use("/nginx/redirection-hosts", redirectionHostsRoutes); router.use('/nginx/redirection-hosts', require('./nginx/redirection_hosts'));
router.use("/nginx/dead-hosts", deadHostsRoutes); router.use('/nginx/dead-hosts', require('./nginx/dead_hosts'));
router.use("/nginx/streams", streamsRoutes); router.use('/nginx/streams', require('./nginx/streams'));
router.use("/nginx/access-lists", accessListsRoutes); router.use('/nginx/access-lists', require('./nginx/access_lists'));
router.use("/nginx/certificates", certificatesHostsRoutes); router.use('/nginx/certificates', require('./nginx/certificates'));
/** /**
* API 404 for all other routes * API 404 for all other routes
* *
* ALL /api/* * ALL /api/*
*/ */
router.all(/(.+)/, (req, _, next) => { router.all(/(.+)/, function (req, _, next) {
req.params.page = req.params["0"]; req.params.page = req.params['0'];
next(new errs.ItemNotFoundError(req.params.page)); next(new error.ItemNotFoundError(req.params.page));
}); });
export default router; module.exports = router;

View File

@@ -1,23 +1,22 @@
import express from "express"; const express = require('express');
import internalAccessList from "../../internal/access-list.js"; const validator = require('../../lib/validator');
import jwtdecode from "../../lib/express/jwt-decode.js"; const jwtdecode = require('../../lib/express/jwt-decode');
import apiValidator from "../../lib/validator/api.js"; const apiValidator = require('../../lib/validator/api');
import validator from "../../lib/validator/index.js"; const internalAccessList = require('../../internal/access-list');
import { express as logger } from "../../logger.js"; const schema = require('../../schema');
import { getValidationSchema } from "../../schema/index.js";
const router = express.Router({ let router = express.Router({
caseSensitive: true, caseSensitive: true,
strict: true, strict: true,
mergeParams: true, mergeParams: true
}); });
/** /**
* /api/nginx/access-lists * /api/nginx/access-lists
*/ */
router router
.route("/") .route('/')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())
@@ -27,31 +26,29 @@ router
* *
* Retrieve all access-lists * Retrieve all access-lists
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator({
const data = await validator(
{
additionalProperties: false, additionalProperties: false,
properties: { properties: {
expand: { expand: {
$ref: "common#/properties/expand", $ref: 'common#/properties/expand'
}, },
query: { query: {
$ref: "common#/properties/query", $ref: 'common#/properties/query'
},
},
},
{
expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null,
query: typeof req.query.query === "string" ? req.query.query : null,
},
);
const rows = await internalAccessList.getAll(res.locals.access, data.expand, data.query);
res.status(200).send(rows);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
} }
}
}, {
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null),
query: (typeof req.query.query === 'string' ? req.query.query : null)
})
.then((data) => {
return internalAccessList.getAll(res.locals.access, data.expand, data.query);
})
.then((rows) => {
res.status(200)
.send(rows);
})
.catch(next);
}) })
/** /**
@@ -59,15 +56,16 @@ router
* *
* Create a new access-list * Create a new access-list
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
try { apiValidator(schema.getValidationSchema('/nginx/access-lists', 'post'), req.body)
const payload = await apiValidator(getValidationSchema("/nginx/access-lists", "post"), req.body); .then((payload) => {
const result = await internalAccessList.create(res.locals.access, payload); return internalAccessList.create(res.locals.access, payload);
res.status(201).send(result); })
} catch (err) { .then((result) => {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); res.status(201)
next(err); .send(result);
} })
.catch(next);
}); });
/** /**
@@ -76,7 +74,7 @@ router
* /api/nginx/access-lists/123 * /api/nginx/access-lists/123
*/ */
router router
.route("/:list_id") .route('/:list_id')
.options((_, res) => { .options((_, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
@@ -87,35 +85,33 @@ router
* *
* Retrieve a specific access-list * Retrieve a specific access-list
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator({
const data = await validator( required: ['list_id'],
{
required: ["list_id"],
additionalProperties: false, additionalProperties: false,
properties: { properties: {
list_id: { list_id: {
$ref: "common#/properties/id", $ref: 'common#/properties/id'
}, },
expand: { expand: {
$ref: "common#/properties/expand", $ref: 'common#/properties/expand'
},
},
},
{
list_id: req.params.list_id,
expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null,
},
);
const row = await internalAccessList.get(res.locals.access, {
id: Number.parseInt(data.list_id, 10),
expand: data.expand,
});
res.status(200).send(row);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
} }
}
}, {
list_id: req.params.list_id,
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null)
})
.then((data) => {
return internalAccessList.get(res.locals.access, {
id: parseInt(data.list_id, 10),
expand: data.expand
});
})
.then((row) => {
res.status(200)
.send(row);
})
.catch(next);
}) })
/** /**
@@ -123,16 +119,17 @@ router
* *
* Update and existing access-list * Update and existing access-list
*/ */
.put(async (req, res, next) => { .put((req, res, next) => {
try { apiValidator(schema.getValidationSchema('/nginx/access-lists/{listID}', 'put'), req.body)
const payload = await apiValidator(getValidationSchema("/nginx/access-lists/{listID}", "put"), req.body); .then((payload) => {
payload.id = Number.parseInt(req.params.list_id, 10); payload.id = parseInt(req.params.list_id, 10);
const result = await internalAccessList.update(res.locals.access, payload); return internalAccessList.update(res.locals.access, payload);
res.status(200).send(result); })
} catch (err) { .then((result) => {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); res.status(200)
next(err); .send(result);
} })
.catch(next);
}) })
/** /**
@@ -140,16 +137,13 @@ router
* *
* Delete and existing access-list * Delete and existing access-list
*/ */
.delete(async (req, res, next) => { .delete((req, res, next) => {
try { internalAccessList.delete(res.locals.access, {id: parseInt(req.params.list_id, 10)})
const result = await internalAccessList.delete(res.locals.access, { .then((result) => {
id: Number.parseInt(req.params.list_id, 10), res.status(200)
}); .send(result);
res.status(200).send(result); })
} catch (err) { .catch(next);
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
export default router; module.exports = router;

View File

@@ -1,23 +1,22 @@
import express from "express"; const express = require('express');
import internalCertificate from "../../internal/certificate.js"; const error = require('../../lib/error');
import errs from "../../lib/error.js"; const validator = require('../../lib/validator');
import jwtdecode from "../../lib/express/jwt-decode.js"; const jwtdecode = require('../../lib/express/jwt-decode');
import apiValidator from "../../lib/validator/api.js"; const apiValidator = require('../../lib/validator/api');
import validator from "../../lib/validator/index.js"; const internalCertificate = require('../../internal/certificate');
import { express as logger } from "../../logger.js"; const schema = require('../../schema');
import { getValidationSchema } from "../../schema/index.js";
const router = express.Router({ let router = express.Router({
caseSensitive: true, caseSensitive: true,
strict: true, strict: true,
mergeParams: true, mergeParams: true
}); });
/** /**
* /api/nginx/certificates * /api/nginx/certificates
*/ */
router router
.route("/") .route('/')
.options((_, res) => { .options((_, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
@@ -28,31 +27,29 @@ router
* *
* Retrieve all certificates * Retrieve all certificates
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator({
const data = await validator(
{
additionalProperties: false, additionalProperties: false,
properties: { properties: {
expand: { expand: {
$ref: "common#/properties/expand", $ref: 'common#/properties/expand'
}, },
query: { query: {
$ref: "common#/properties/query", $ref: 'common#/properties/query'
},
},
},
{
expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null,
query: typeof req.query.query === "string" ? req.query.query : null,
},
);
const rows = await internalCertificate.getAll(res.locals.access, data.expand, data.query);
res.status(200).send(rows);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
} }
}
}, {
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null),
query: (typeof req.query.query === 'string' ? req.query.query : null)
})
.then((data) => {
return internalCertificate.getAll(res.locals.access, data.expand, data.query);
})
.then((rows) => {
res.status(200)
.send(rows);
})
.catch(next);
}) })
/** /**
@@ -60,16 +57,17 @@ router
* *
* Create a new certificate * Create a new certificate
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
try { apiValidator(schema.getValidationSchema('/nginx/certificates', 'post'), req.body)
const payload = await apiValidator(getValidationSchema("/nginx/certificates", "post"), req.body); .then((payload) => {
req.setTimeout(900000); // 15 minutes timeout req.setTimeout(900000); // 15 minutes timeout
const result = await internalCertificate.create(res.locals.access, payload); return internalCertificate.create(res.locals.access, payload);
res.status(201).send(result); })
} catch (err) { .then((result) => {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); res.status(201)
next(err); .send(result);
} })
.catch(next);
}); });
/** /**
@@ -78,7 +76,7 @@ router
* /api/nginx/certificates/test-http * /api/nginx/certificates/test-http
*/ */
router router
.route("/test-http") .route('/test-http')
.options((_, res) => { .options((_, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
@@ -89,22 +87,18 @@ router
* *
* Test HTTP challenge for domains * Test HTTP challenge for domains
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
if (req.query.domains === undefined) { if (req.query.domains === undefined) {
next(new errs.ValidationError("Domains are required as query parameters")); next(new error.ValidationError('Domains are required as query parameters'));
return; return;
} }
try { internalCertificate.testHttpsChallenge(res.locals.access, JSON.parse(req.query.domains))
const result = await internalCertificate.testHttpsChallenge( .then((result) => {
res.locals.access, res.status(200)
JSON.parse(req.query.domains), .send(result);
); })
res.status(200).send(result); .catch(next);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
/** /**
@@ -113,7 +107,7 @@ router
* /api/nginx/certificates/123 * /api/nginx/certificates/123
*/ */
router router
.route("/:certificate_id") .route('/:certificate_id')
.options((_, res) => { .options((_, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
@@ -124,35 +118,33 @@ router
* *
* Retrieve a specific certificate * Retrieve a specific certificate
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator({
const data = await validator( required: ['certificate_id'],
{
required: ["certificate_id"],
additionalProperties: false, additionalProperties: false,
properties: { properties: {
certificate_id: { certificate_id: {
$ref: "common#/properties/id", $ref: 'common#/properties/id'
}, },
expand: { expand: {
$ref: "common#/properties/expand", $ref: 'common#/properties/expand'
},
},
},
{
certificate_id: req.params.certificate_id,
expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null,
},
);
const row = await internalCertificate.get(res.locals.access, {
id: Number.parseInt(data.certificate_id, 10),
expand: data.expand,
});
res.status(200).send(row);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
} }
}
}, {
certificate_id: req.params.certificate_id,
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null)
})
.then((data) => {
return internalCertificate.get(res.locals.access, {
id: parseInt(data.certificate_id, 10),
expand: data.expand
});
})
.then((row) => {
res.status(200)
.send(row);
})
.catch(next);
}) })
/** /**
@@ -160,16 +152,13 @@ router
* *
* Update and existing certificate * Update and existing certificate
*/ */
.delete(async (req, res, next) => { .delete((req, res, next) => {
try { internalCertificate.delete(res.locals.access, {id: parseInt(req.params.certificate_id, 10)})
const result = await internalCertificate.delete(res.locals.access, { .then((result) => {
id: Number.parseInt(req.params.certificate_id, 10), res.status(200)
}); .send(result);
res.status(200).send(result); })
} catch (err) { .catch(next);
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
/** /**
@@ -178,7 +167,7 @@ router
* /api/nginx/certificates/123/upload * /api/nginx/certificates/123/upload
*/ */
router router
.route("/:certificate_id/upload") .route('/:certificate_id/upload')
.options((_, res) => { .options((_, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
@@ -189,21 +178,20 @@ router
* *
* Upload certificates * Upload certificates
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
if (!req.files) { if (!req.files) {
res.status(400).send({ error: "No files were uploaded" }); res.status(400)
return; .send({error: 'No files were uploaded'});
} } else {
internalCertificate.upload(res.locals.access, {
try { id: parseInt(req.params.certificate_id, 10),
const result = await internalCertificate.upload(res.locals.access, { files: req.files
id: Number.parseInt(req.params.certificate_id, 10), })
files: req.files, .then((result) => {
}); res.status(200)
res.status(200).send(result); .send(result);
} catch (err) { })
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); .catch(next);
next(err);
} }
}); });
@@ -213,7 +201,7 @@ router
* /api/nginx/certificates/123/renew * /api/nginx/certificates/123/renew
*/ */
router router
.route("/:certificate_id/renew") .route('/:certificate_id/renew')
.options((_, res) => { .options((_, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
@@ -224,17 +212,16 @@ router
* *
* Renew certificate * Renew certificate
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
req.setTimeout(900000); // 15 minutes timeout req.setTimeout(900000); // 15 minutes timeout
try { internalCertificate.renew(res.locals.access, {
const result = await internalCertificate.renew(res.locals.access, { id: parseInt(req.params.certificate_id, 10)
id: Number.parseInt(req.params.certificate_id, 10), })
}); .then((result) => {
res.status(200).send(result); res.status(200)
} catch (err) { .send(result);
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); })
next(err); .catch(next);
}
}); });
/** /**
@@ -243,8 +230,8 @@ router
* /api/nginx/certificates/123/download * /api/nginx/certificates/123/download
*/ */
router router
.route("/:certificate_id/download") .route('/:certificate_id/download')
.options((_req, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())
@@ -254,16 +241,15 @@ router
* *
* Renew certificate * Renew certificate
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { internalCertificate.download(res.locals.access, {
const result = await internalCertificate.download(res.locals.access, { id: parseInt(req.params.certificate_id, 10)
id: Number.parseInt(req.params.certificate_id, 10), })
}); .then((result) => {
res.status(200).download(result.fileName); res.status(200)
} catch (err) { .download(result.fileName);
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); })
next(err); .catch(next);
}
}); });
/** /**
@@ -272,7 +258,7 @@ router
* /api/nginx/certificates/validate * /api/nginx/certificates/validate
*/ */
router router
.route("/validate") .route('/validate')
.options((_, res) => { .options((_, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
@@ -283,21 +269,20 @@ router
* *
* Validate certificates * Validate certificates
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
if (!req.files) { if (!req.files) {
res.status(400).send({ error: "No files were uploaded" }); res.status(400)
return; .send({error: 'No files were uploaded'});
} } else {
internalCertificate.validate({
try { files: req.files
const result = await internalCertificate.validate({ })
files: req.files, .then((result) => {
}); res.status(200)
res.status(200).send(result); .send(result);
} catch (err) { })
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); .catch(next);
next(err);
} }
}); });
export default router; module.exports = router;

View File

@@ -1,22 +1,21 @@
import express from "express"; const express = require('express');
import internalDeadHost from "../../internal/dead-host.js"; const validator = require('../../lib/validator');
import jwtdecode from "../../lib/express/jwt-decode.js"; const jwtdecode = require('../../lib/express/jwt-decode');
import apiValidator from "../../lib/validator/api.js"; const apiValidator = require('../../lib/validator/api');
import validator from "../../lib/validator/index.js"; const internalDeadHost = require('../../internal/dead-host');
import { express as logger } from "../../logger.js"; const schema = require('../../schema');
import { getValidationSchema } from "../../schema/index.js";
const router = express.Router({ let router = express.Router({
caseSensitive: true, caseSensitive: true,
strict: true, strict: true,
mergeParams: true, mergeParams: true
}); });
/** /**
* /api/nginx/dead-hosts * /api/nginx/dead-hosts
*/ */
router router
.route("/") .route('/')
.options((_, res) => { .options((_, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
@@ -27,31 +26,29 @@ router
* *
* Retrieve all dead-hosts * Retrieve all dead-hosts
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator({
const data = await validator(
{
additionalProperties: false, additionalProperties: false,
properties: { properties: {
expand: { expand: {
$ref: "common#/properties/expand", $ref: 'common#/properties/expand'
}, },
query: { query: {
$ref: "common#/properties/query", $ref: 'common#/properties/query'
},
},
},
{
expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null,
query: typeof req.query.query === "string" ? req.query.query : null,
},
);
const rows = await internalDeadHost.getAll(res.locals.access, data.expand, data.query);
res.status(200).send(rows);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
} }
}
}, {
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null),
query: (typeof req.query.query === 'string' ? req.query.query : null)
})
.then((data) => {
return internalDeadHost.getAll(res.locals.access, data.expand, data.query);
})
.then((rows) => {
res.status(200)
.send(rows);
})
.catch(next);
}) })
/** /**
@@ -59,15 +56,16 @@ router
* *
* Create a new dead-host * Create a new dead-host
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
try { apiValidator(schema.getValidationSchema('/nginx/dead-hosts', 'post'), req.body)
const payload = await apiValidator(getValidationSchema("/nginx/dead-hosts", "post"), req.body); .then((payload) => {
const result = await internalDeadHost.create(res.locals.access, payload); return internalDeadHost.create(res.locals.access, payload);
res.status(201).send(result); })
} catch (err) { .then((result) => {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); res.status(201)
next(err); .send(result);
} })
.catch(next);
}); });
/** /**
@@ -76,8 +74,8 @@ router
* /api/nginx/dead-hosts/123 * /api/nginx/dead-hosts/123
*/ */
router router
.route("/:host_id") .route('/:host_id')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())
@@ -87,35 +85,33 @@ router
* *
* Retrieve a specific dead-host * Retrieve a specific dead-host
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator({
const data = await validator( required: ['host_id'],
{
required: ["host_id"],
additionalProperties: false, additionalProperties: false,
properties: { properties: {
host_id: { host_id: {
$ref: "common#/properties/id", $ref: 'common#/properties/id'
}, },
expand: { expand: {
$ref: "common#/properties/expand", $ref: 'common#/properties/expand'
},
},
},
{
host_id: req.params.host_id,
expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null,
},
);
const row = await internalDeadHost.get(res.locals.access, {
id: Number.parseInt(data.host_id, 10),
expand: data.expand,
});
res.status(200).send(row);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
} }
}
}, {
host_id: req.params.host_id,
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null)
})
.then((data) => {
return internalDeadHost.get(res.locals.access, {
id: parseInt(data.host_id, 10),
expand: data.expand
});
})
.then((row) => {
res.status(200)
.send(row);
})
.catch(next);
}) })
/** /**
@@ -123,16 +119,17 @@ router
* *
* Update and existing dead-host * Update and existing dead-host
*/ */
.put(async (req, res, next) => { .put((req, res, next) => {
try { apiValidator(schema.getValidationSchema('/nginx/dead-hosts/{hostID}', 'put'), req.body)
const payload = await apiValidator(getValidationSchema("/nginx/dead-hosts/{hostID}", "put"), req.body); .then((payload) => {
payload.id = Number.parseInt(req.params.host_id, 10); payload.id = parseInt(req.params.host_id, 10);
const result = await internalDeadHost.update(res.locals.access, payload); return internalDeadHost.update(res.locals.access, payload);
res.status(200).send(result); })
} catch (err) { .then((result) => {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); res.status(200)
next(err); .send(result);
} })
.catch(next);
}) })
/** /**
@@ -140,16 +137,13 @@ router
* *
* Update and existing dead-host * Update and existing dead-host
*/ */
.delete(async (req, res, next) => { .delete((req, res, next) => {
try { internalDeadHost.delete(res.locals.access, {id: parseInt(req.params.host_id, 10)})
const result = await internalDeadHost.delete(res.locals.access, { .then((result) => {
id: Number.parseInt(req.params.host_id, 10), res.status(200)
}); .send(result);
res.status(200).send(result); })
} catch (err) { .catch(next);
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
/** /**
@@ -158,7 +152,7 @@ router
* /api/nginx/dead-hosts/123/enable * /api/nginx/dead-hosts/123/enable
*/ */
router router
.route("/:host_id/enable") .route('/:host_id/enable')
.options((_, res) => { .options((_, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
@@ -167,16 +161,13 @@ router
/** /**
* POST /api/nginx/dead-hosts/123/enable * POST /api/nginx/dead-hosts/123/enable
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
try { internalDeadHost.enable(res.locals.access, {id: parseInt(req.params.host_id, 10)})
const result = await internalDeadHost.enable(res.locals.access, { .then((result) => {
id: Number.parseInt(req.params.host_id, 10), res.status(200)
}); .send(result);
res.status(200).send(result); })
} catch (err) { .catch(next);
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
/** /**
@@ -185,7 +176,7 @@ router
* /api/nginx/dead-hosts/123/disable * /api/nginx/dead-hosts/123/disable
*/ */
router router
.route("/:host_id/disable") .route('/:host_id/disable')
.options((_, res) => { .options((_, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
@@ -195,13 +186,12 @@ router
* POST /api/nginx/dead-hosts/123/disable * POST /api/nginx/dead-hosts/123/disable
*/ */
.post((req, res, next) => { .post((req, res, next) => {
try { internalDeadHost.disable(res.locals.access, {id: parseInt(req.params.host_id, 10)})
const result = internalDeadHost.disable(res.locals.access, { id: Number.parseInt(req.params.host_id, 10) }); .then((result) => {
res.status(200).send(result); res.status(200)
} catch (err) { .send(result);
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); })
next(err); .catch(next);
}
}); });
export default router; module.exports = router;

View File

@@ -1,23 +1,22 @@
import express from "express"; const express = require('express');
import internalProxyHost from "../../internal/proxy-host.js"; const validator = require('../../lib/validator');
import jwtdecode from "../../lib/express/jwt-decode.js"; const jwtdecode = require('../../lib/express/jwt-decode');
import apiValidator from "../../lib/validator/api.js"; const apiValidator = require('../../lib/validator/api');
import validator from "../../lib/validator/index.js"; const internalProxyHost = require('../../internal/proxy-host');
import { express as logger } from "../../logger.js"; const schema = require('../../schema');
import { getValidationSchema } from "../../schema/index.js";
const router = express.Router({ let router = express.Router({
caseSensitive: true, caseSensitive: true,
strict: true, strict: true,
mergeParams: true, mergeParams: true
}); });
/** /**
* /api/nginx/proxy-hosts * /api/nginx/proxy-hosts
*/ */
router router
.route("/") .route('/')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())
@@ -27,31 +26,29 @@ router
* *
* Retrieve all proxy-hosts * Retrieve all proxy-hosts
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator({
const data = await validator(
{
additionalProperties: false, additionalProperties: false,
properties: { properties: {
expand: { expand: {
$ref: "common#/properties/expand", $ref: 'common#/properties/expand'
}, },
query: { query: {
$ref: "common#/properties/query", $ref: 'common#/properties/query'
},
},
},
{
expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null,
query: typeof req.query.query === "string" ? req.query.query : null,
},
);
const rows = await internalProxyHost.getAll(res.locals.access, data.expand, data.query);
res.status(200).send(rows);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
} }
}
}, {
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null),
query: (typeof req.query.query === 'string' ? req.query.query : null)
})
.then((data) => {
return internalProxyHost.getAll(res.locals.access, data.expand, data.query);
})
.then((rows) => {
res.status(200)
.send(rows);
})
.catch(next);
}) })
/** /**
@@ -59,15 +56,16 @@ router
* *
* Create a new proxy-host * Create a new proxy-host
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
try { apiValidator(schema.getValidationSchema('/nginx/proxy-hosts', 'post'), req.body)
const payload = await apiValidator(getValidationSchema("/nginx/proxy-hosts", "post"), req.body); .then((payload) => {
const result = await internalProxyHost.create(res.locals.access, payload); return internalProxyHost.create(res.locals.access, payload);
res.status(201).send(result); })
} catch (err) { .then((result) => {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); res.status(201)
next(err); .send(result);
} })
.catch(next);
}); });
/** /**
@@ -76,8 +74,8 @@ router
* /api/nginx/proxy-hosts/123 * /api/nginx/proxy-hosts/123
*/ */
router router
.route("/:host_id") .route('/:host_id')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())
@@ -87,35 +85,33 @@ router
* *
* Retrieve a specific proxy-host * Retrieve a specific proxy-host
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator({
const data = await validator( required: ['host_id'],
{
required: ["host_id"],
additionalProperties: false, additionalProperties: false,
properties: { properties: {
host_id: { host_id: {
$ref: "common#/properties/id", $ref: 'common#/properties/id'
}, },
expand: { expand: {
$ref: "common#/properties/expand", $ref: 'common#/properties/expand'
},
},
},
{
host_id: req.params.host_id,
expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null,
},
);
const row = await internalProxyHost.get(res.locals.access, {
id: Number.parseInt(data.host_id, 10),
expand: data.expand,
});
res.status(200).send(row);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
} }
}
}, {
host_id: req.params.host_id,
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null)
})
.then((data) => {
return internalProxyHost.get(res.locals.access, {
id: parseInt(data.host_id, 10),
expand: data.expand
});
})
.then((row) => {
res.status(200)
.send(row);
})
.catch(next);
}) })
/** /**
@@ -123,16 +119,17 @@ router
* *
* Update and existing proxy-host * Update and existing proxy-host
*/ */
.put(async (req, res, next) => { .put((req, res, next) => {
try { apiValidator(schema.getValidationSchema('/nginx/proxy-hosts/{hostID}', 'put'), req.body)
const payload = await apiValidator(getValidationSchema("/nginx/proxy-hosts/{hostID}", "put"), req.body); .then((payload) => {
payload.id = Number.parseInt(req.params.host_id, 10); payload.id = parseInt(req.params.host_id, 10);
const result = await internalProxyHost.update(res.locals.access, payload); return internalProxyHost.update(res.locals.access, payload);
res.status(200).send(result); })
} catch (err) { .then((result) => {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); res.status(200)
next(err); .send(result);
} })
.catch(next);
}) })
/** /**
@@ -140,16 +137,13 @@ router
* *
* Update and existing proxy-host * Update and existing proxy-host
*/ */
.delete(async (req, res, next) => { .delete((req, res, next) => {
try { internalProxyHost.delete(res.locals.access, {id: parseInt(req.params.host_id, 10)})
const result = await internalProxyHost.delete(res.locals.access, { .then((result) => {
id: Number.parseInt(req.params.host_id, 10), res.status(200)
}); .send(result);
res.status(200).send(result); })
} catch (err) { .catch(next);
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
/** /**
@@ -158,7 +152,7 @@ router
* /api/nginx/proxy-hosts/123/enable * /api/nginx/proxy-hosts/123/enable
*/ */
router router
.route("/:host_id/enable") .route('/:host_id/enable')
.options((_, res) => { .options((_, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
@@ -167,16 +161,13 @@ router
/** /**
* POST /api/nginx/proxy-hosts/123/enable * POST /api/nginx/proxy-hosts/123/enable
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
try { internalProxyHost.enable(res.locals.access, {id: parseInt(req.params.host_id, 10)})
const result = await internalProxyHost.enable(res.locals.access, { .then((result) => {
id: Number.parseInt(req.params.host_id, 10), res.status(200)
}); .send(result);
res.status(200).send(result); })
} catch (err) { .catch(next);
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
/** /**
@@ -185,7 +176,7 @@ router
* /api/nginx/proxy-hosts/123/disable * /api/nginx/proxy-hosts/123/disable
*/ */
router router
.route("/:host_id/disable") .route('/:host_id/disable')
.options((_, res) => { .options((_, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
@@ -194,16 +185,13 @@ router
/** /**
* POST /api/nginx/proxy-hosts/123/disable * POST /api/nginx/proxy-hosts/123/disable
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
try { internalProxyHost.disable(res.locals.access, {id: parseInt(req.params.host_id, 10)})
const result = await internalProxyHost.disable(res.locals.access, { .then((result) => {
id: Number.parseInt(req.params.host_id, 10), res.status(200)
}); .send(result);
res.status(200).send(result); })
} catch (err) { .catch(next);
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
export default router; module.exports = router;

View File

@@ -1,23 +1,22 @@
import express from "express"; const express = require('express');
import internalRedirectionHost from "../../internal/redirection-host.js"; const validator = require('../../lib/validator');
import jwtdecode from "../../lib/express/jwt-decode.js"; const jwtdecode = require('../../lib/express/jwt-decode');
import apiValidator from "../../lib/validator/api.js"; const apiValidator = require('../../lib/validator/api');
import validator from "../../lib/validator/index.js"; const internalRedirectionHost = require('../../internal/redirection-host');
import { express as logger } from "../../logger.js"; const schema = require('../../schema');
import { getValidationSchema } from "../../schema/index.js";
const router = express.Router({ let router = express.Router({
caseSensitive: true, caseSensitive: true,
strict: true, strict: true,
mergeParams: true, mergeParams: true
}); });
/** /**
* /api/nginx/redirection-hosts * /api/nginx/redirection-hosts
*/ */
router router
.route("/") .route('/')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())
@@ -27,31 +26,29 @@ router
* *
* Retrieve all redirection-hosts * Retrieve all redirection-hosts
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator({
const data = await validator(
{
additionalProperties: false, additionalProperties: false,
properties: { properties: {
expand: { expand: {
$ref: "common#/properties/expand", $ref: 'common#/properties/expand'
}, },
query: { query: {
$ref: "common#/properties/query", $ref: 'common#/properties/query'
},
},
},
{
expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null,
query: typeof req.query.query === "string" ? req.query.query : null,
},
);
const rows = await internalRedirectionHost.getAll(res.locals.access, data.expand, data.query);
res.status(200).send(rows);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
} }
}
}, {
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null),
query: (typeof req.query.query === 'string' ? req.query.query : null)
})
.then((data) => {
return internalRedirectionHost.getAll(res.locals.access, data.expand, data.query);
})
.then((rows) => {
res.status(200)
.send(rows);
})
.catch(next);
}) })
/** /**
@@ -59,15 +56,16 @@ router
* *
* Create a new redirection-host * Create a new redirection-host
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
try { apiValidator(schema.getValidationSchema('/nginx/redirection-hosts', 'post'), req.body)
const payload = await apiValidator(getValidationSchema("/nginx/redirection-hosts", "post"), req.body); .then((payload) => {
const result = await internalRedirectionHost.create(res.locals.access, payload); return internalRedirectionHost.create(res.locals.access, payload);
res.status(201).send(result); })
} catch (err) { .then((result) => {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); res.status(201)
next(err); .send(result);
} })
.catch(next);
}); });
/** /**
@@ -76,8 +74,8 @@ router
* /api/nginx/redirection-hosts/123 * /api/nginx/redirection-hosts/123
*/ */
router router
.route("/:host_id") .route('/:host_id')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())
@@ -87,35 +85,33 @@ router
* *
* Retrieve a specific redirection-host * Retrieve a specific redirection-host
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator({
const data = await validator( required: ['host_id'],
{
required: ["host_id"],
additionalProperties: false, additionalProperties: false,
properties: { properties: {
host_id: { host_id: {
$ref: "common#/properties/id", $ref: 'common#/properties/id'
}, },
expand: { expand: {
$ref: "common#/properties/expand", $ref: 'common#/properties/expand'
},
},
},
{
host_id: req.params.host_id,
expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null,
},
);
const row = await internalRedirectionHost.get(res.locals.access, {
id: Number.parseInt(data.host_id, 10),
expand: data.expand,
});
res.status(200).send(row);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
} }
}
}, {
host_id: req.params.host_id,
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null)
})
.then((data) => {
return internalRedirectionHost.get(res.locals.access, {
id: parseInt(data.host_id, 10),
expand: data.expand
});
})
.then((row) => {
res.status(200)
.send(row);
})
.catch(next);
}) })
/** /**
@@ -123,19 +119,17 @@ router
* *
* Update and existing redirection-host * Update and existing redirection-host
*/ */
.put(async (req, res, next) => { .put((req, res, next) => {
try { apiValidator(schema.getValidationSchema('/nginx/redirection-hosts/{hostID}', 'put'), req.body)
const payload = await apiValidator( .then((payload) => {
getValidationSchema("/nginx/redirection-hosts/{hostID}", "put"), payload.id = parseInt(req.params.host_id, 10);
req.body, return internalRedirectionHost.update(res.locals.access, payload);
); })
payload.id = Number.parseInt(req.params.host_id, 10); .then((result) => {
const result = await internalRedirectionHost.update(res.locals.access, payload); res.status(200)
res.status(200).send(result); .send(result);
} catch (err) { })
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); .catch(next);
next(err);
}
}) })
/** /**
@@ -143,16 +137,13 @@ router
* *
* Update and existing redirection-host * Update and existing redirection-host
*/ */
.delete(async (req, res, next) => { .delete((req, res, next) => {
try { internalRedirectionHost.delete(res.locals.access, {id: parseInt(req.params.host_id, 10)})
const result = await internalRedirectionHost.delete(res.locals.access, { .then((result) => {
id: Number.parseInt(req.params.host_id, 10), res.status(200)
}); .send(result);
res.status(200).send(result); })
} catch (err) { .catch(next);
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
/** /**
@@ -161,8 +152,8 @@ router
* /api/nginx/redirection-hosts/123/enable * /api/nginx/redirection-hosts/123/enable
*/ */
router router
.route("/:host_id/enable") .route('/:host_id/enable')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())
@@ -170,16 +161,13 @@ router
/** /**
* POST /api/nginx/redirection-hosts/123/enable * POST /api/nginx/redirection-hosts/123/enable
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
try { internalRedirectionHost.enable(res.locals.access, {id: parseInt(req.params.host_id, 10)})
const result = await internalRedirectionHost.enable(res.locals.access, { .then((result) => {
id: Number.parseInt(req.params.host_id, 10), res.status(200)
}); .send(result);
res.status(200).send(result); })
} catch (err) { .catch(next);
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
/** /**
@@ -188,8 +176,8 @@ router
* /api/nginx/redirection-hosts/123/disable * /api/nginx/redirection-hosts/123/disable
*/ */
router router
.route("/:host_id/disable") .route('/:host_id/disable')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())
@@ -197,16 +185,13 @@ router
/** /**
* POST /api/nginx/redirection-hosts/123/disable * POST /api/nginx/redirection-hosts/123/disable
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
try { internalRedirectionHost.disable(res.locals.access, {id: parseInt(req.params.host_id, 10)})
const result = await internalRedirectionHost.disable(res.locals.access, { .then((result) => {
id: Number.parseInt(req.params.host_id, 10), res.status(200)
}); .send(result);
res.status(200).send(result); })
} catch (err) { .catch(next);
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
export default router; module.exports = router;

View File

@@ -1,23 +1,22 @@
import express from "express"; const express = require('express');
import internalStream from "../../internal/stream.js"; const validator = require('../../lib/validator');
import jwtdecode from "../../lib/express/jwt-decode.js"; const jwtdecode = require('../../lib/express/jwt-decode');
import apiValidator from "../../lib/validator/api.js"; const apiValidator = require('../../lib/validator/api');
import validator from "../../lib/validator/index.js"; const internalStream = require('../../internal/stream');
import { express as logger } from "../../logger.js"; const schema = require('../../schema');
import { getValidationSchema } from "../../schema/index.js";
const router = express.Router({ let router = express.Router({
caseSensitive: true, caseSensitive: true,
strict: true, strict: true,
mergeParams: true, mergeParams: true
}); });
/** /**
* /api/nginx/streams * /api/nginx/streams
*/ */
router router
.route("/") .route('/')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) // preferred so it doesn't apply to nonexistent routes .all(jwtdecode()) // preferred so it doesn't apply to nonexistent routes
@@ -27,31 +26,29 @@ router
* *
* Retrieve all streams * Retrieve all streams
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator({
const data = await validator(
{
additionalProperties: false, additionalProperties: false,
properties: { properties: {
expand: { expand: {
$ref: "common#/properties/expand", $ref: 'common#/properties/expand'
}, },
query: { query: {
$ref: "common#/properties/query", $ref: 'common#/properties/query'
},
},
},
{
expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null,
query: typeof req.query.query === "string" ? req.query.query : null,
},
);
const rows = await internalStream.getAll(res.locals.access, data.expand, data.query);
res.status(200).send(rows);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
} }
}
}, {
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null),
query: (typeof req.query.query === 'string' ? req.query.query : null)
})
.then((data) => {
return internalStream.getAll(res.locals.access, data.expand, data.query);
})
.then((rows) => {
res.status(200)
.send(rows);
})
.catch(next);
}) })
/** /**
@@ -59,15 +56,16 @@ router
* *
* Create a new stream * Create a new stream
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
try { apiValidator(schema.getValidationSchema('/nginx/streams', 'post'), req.body)
const payload = await apiValidator(getValidationSchema("/nginx/streams", "post"), req.body); .then((payload) => {
const result = await internalStream.create(res.locals.access, payload); return internalStream.create(res.locals.access, payload);
res.status(201).send(result); })
} catch (err) { .then((result) => {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); res.status(201)
next(err); .send(result);
} })
.catch(next);
}); });
/** /**
@@ -76,8 +74,8 @@ router
* /api/nginx/streams/123 * /api/nginx/streams/123
*/ */
router router
.route("/:stream_id") .route('/:stream_id')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) // preferred so it doesn't apply to nonexistent routes .all(jwtdecode()) // preferred so it doesn't apply to nonexistent routes
@@ -87,35 +85,33 @@ router
* *
* Retrieve a specific stream * Retrieve a specific stream
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator({
const data = await validator( required: ['stream_id'],
{
required: ["stream_id"],
additionalProperties: false, additionalProperties: false,
properties: { properties: {
stream_id: { stream_id: {
$ref: "common#/properties/id", $ref: 'common#/properties/id'
}, },
expand: { expand: {
$ref: "common#/properties/expand", $ref: 'common#/properties/expand'
},
},
},
{
stream_id: req.params.stream_id,
expand: typeof req.query.expand === "string" ? req.query.expand.split(",") : null,
},
);
const row = await internalStream.get(res.locals.access, {
id: Number.parseInt(data.stream_id, 10),
expand: data.expand,
});
res.status(200).send(row);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
} }
}
}, {
stream_id: req.params.stream_id,
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null)
})
.then((data) => {
return internalStream.get(res.locals.access, {
id: parseInt(data.stream_id, 10),
expand: data.expand
});
})
.then((row) => {
res.status(200)
.send(row);
})
.catch(next);
}) })
/** /**
@@ -123,16 +119,17 @@ router
* *
* Update and existing stream * Update and existing stream
*/ */
.put(async (req, res, next) => { .put((req, res, next) => {
try { apiValidator(schema.getValidationSchema('/nginx/streams/{streamID}', 'put'), req.body)
const payload = await apiValidator(getValidationSchema("/nginx/streams/{streamID}", "put"), req.body); .then((payload) => {
payload.id = Number.parseInt(req.params.stream_id, 10); payload.id = parseInt(req.params.stream_id, 10);
const result = await internalStream.update(res.locals.access, payload); return internalStream.update(res.locals.access, payload);
res.status(200).send(result); })
} catch (err) { .then((result) => {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); res.status(200)
next(err); .send(result);
} })
.catch(next);
}) })
/** /**
@@ -140,16 +137,13 @@ router
* *
* Update and existing stream * Update and existing stream
*/ */
.delete(async (req, res, next) => { .delete((req, res, next) => {
try { internalStream.delete(res.locals.access, {id: parseInt(req.params.stream_id, 10)})
const result = await internalStream.delete(res.locals.access, { .then((result) => {
id: Number.parseInt(req.params.stream_id, 10), res.status(200)
}); .send(result);
res.status(200).send(result); })
} catch (err) { .catch(next);
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
/** /**
@@ -158,7 +152,7 @@ router
* /api/nginx/streams/123/enable * /api/nginx/streams/123/enable
*/ */
router router
.route("/:host_id/enable") .route('/:host_id/enable')
.options((_, res) => { .options((_, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
@@ -167,16 +161,13 @@ router
/** /**
* POST /api/nginx/streams/123/enable * POST /api/nginx/streams/123/enable
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
try { internalStream.enable(res.locals.access, {id: parseInt(req.params.host_id, 10)})
const result = await internalStream.enable(res.locals.access, { .then((result) => {
id: Number.parseInt(req.params.host_id, 10), res.status(200)
}); .send(result);
res.status(200).send(result); })
} catch (err) { .catch(next);
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
/** /**
@@ -185,7 +176,7 @@ router
* /api/nginx/streams/123/disable * /api/nginx/streams/123/disable
*/ */
router router
.route("/:host_id/disable") .route('/:host_id/disable')
.options((_, res) => { .options((_, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
@@ -194,16 +185,13 @@ router
/** /**
* POST /api/nginx/streams/123/disable * POST /api/nginx/streams/123/disable
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
try { internalStream.disable(res.locals.access, {id: parseInt(req.params.host_id, 10)})
const result = await internalStream.disable(res.locals.access, { .then((result) => {
id: Number.parseInt(req.params.host_id, 10), res.status(200)
}); .send(result);
res.status(200).send(result); })
} catch (err) { .catch(next);
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
export default router; module.exports = router;

View File

@@ -1,16 +1,15 @@
import express from "express"; const express = require('express');
import internalReport from "../internal/report.js"; const jwtdecode = require('../lib/express/jwt-decode');
import jwtdecode from "../lib/express/jwt-decode.js"; const internalReport = require('../internal/report');
import { express as logger } from "../logger.js";
const router = express.Router({ let router = express.Router({
caseSensitive: true, caseSensitive: true,
strict: true, strict: true,
mergeParams: true, mergeParams: true
}); });
router router
.route("/hosts") .route('/hosts')
.options((_, res) => { .options((_, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
@@ -18,14 +17,13 @@ router
/** /**
* GET /reports/hosts * GET /reports/hosts
*/ */
.get(jwtdecode(), async (req, res, next) => { .get(jwtdecode(), (_, res, next) => {
try { internalReport.getHostsReport(res.locals.access)
const data = await internalReport.getHostsReport(res.locals.access); .then((data) => {
res.status(200).send(data); res.status(200)
} catch (err) { .send(data);
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); })
next(err); .catch(next);
}
}); });
export default router; module.exports = router;

View File

@@ -1,16 +1,15 @@
import express from "express"; const express = require('express');
import { express as logger } from "../logger.js"; const schema = require('../schema');
import PACKAGE from "../package.json" with { type: "json" }; const PACKAGE = require('../package.json');
import { getCompiledSchema } from "../schema/index.js";
const router = express.Router({ const router = express.Router({
caseSensitive: true, caseSensitive: true,
strict: true, strict: true,
mergeParams: true, mergeParams: true
}); });
router router
.route("/") .route('/')
.options((_, res) => { .options((_, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
@@ -19,26 +18,21 @@ router
* GET /schema * GET /schema
*/ */
.get(async (req, res) => { .get(async (req, res) => {
try { let swaggerJSON = await schema.getCompiledSchema();
const swaggerJSON = await getCompiledSchema();
let proto = req.protocol; let proto = req.protocol;
if (typeof req.headers["x-forwarded-proto"] !== "undefined" && req.headers["x-forwarded-proto"]) { if (typeof req.headers['x-forwarded-proto'] !== 'undefined' && req.headers['x-forwarded-proto']) {
proto = req.headers["x-forwarded-proto"]; proto = req.headers['x-forwarded-proto'];
} }
let origin = `${proto}://${req.hostname}`; let origin = proto + '://' + req.hostname;
if (typeof req.headers.origin !== "undefined" && req.headers.origin) { if (typeof req.headers.origin !== 'undefined' && req.headers.origin) {
origin = req.headers.origin; origin = req.headers.origin;
} }
swaggerJSON.info.version = PACKAGE.version; swaggerJSON.info.version = PACKAGE.version;
swaggerJSON.servers[0].url = `${origin}/api`; swaggerJSON.servers[0].url = origin + '/api';
res.status(200).send(swaggerJSON); res.status(200).send(swaggerJSON);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
export default router; module.exports = router;

View File

@@ -1,22 +1,21 @@
import express from "express"; const express = require('express');
import internalSetting from "../internal/setting.js"; const validator = require('../lib/validator');
import jwtdecode from "../lib/express/jwt-decode.js"; const jwtdecode = require('../lib/express/jwt-decode');
import apiValidator from "../lib/validator/api.js"; const apiValidator = require('../lib/validator/api');
import validator from "../lib/validator/index.js"; const internalSetting = require('../internal/setting');
import { express as logger } from "../logger.js"; const schema = require('../schema');
import { getValidationSchema } from "../schema/index.js";
const router = express.Router({ let router = express.Router({
caseSensitive: true, caseSensitive: true,
strict: true, strict: true,
mergeParams: true, mergeParams: true
}); });
/** /**
* /api/settings * /api/settings
*/ */
router router
.route("/") .route('/')
.options((_, res) => { .options((_, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
@@ -27,14 +26,13 @@ router
* *
* Retrieve all settings * Retrieve all settings
*/ */
.get(async (req, res, next) => { .get((_, res, next) => {
try { internalSetting.getAll(res.locals.access)
const rows = await internalSetting.getAll(res.locals.access); .then((rows) => {
res.status(200).send(rows); res.status(200)
} catch (err) { .send(rows);
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); })
next(err); .catch(next);
}
}); });
/** /**
@@ -43,7 +41,7 @@ router
* /api/settings/something * /api/settings/something
*/ */
router router
.route("/:setting_id") .route('/:setting_id')
.options((_, res) => { .options((_, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
@@ -54,31 +52,29 @@ router
* *
* Retrieve a specific setting * Retrieve a specific setting
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator({
const data = await validator( required: ['setting_id'],
{
required: ["setting_id"],
additionalProperties: false, additionalProperties: false,
properties: { properties: {
setting_id: { setting_id: {
type: "string", type: 'string',
minLength: 1, minLength: 1
},
},
},
{
setting_id: req.params.setting_id,
},
);
const row = await internalSetting.get(res.locals.access, {
id: data.setting_id,
});
res.status(200).send(row);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
} }
}
}, {
setting_id: req.params.setting_id
})
.then((data) => {
return internalSetting.get(res.locals.access, {
id: data.setting_id
});
})
.then((row) => {
res.status(200)
.send(row);
})
.catch(next);
}) })
/** /**
@@ -86,16 +82,17 @@ router
* *
* Update and existing setting * Update and existing setting
*/ */
.put(async (req, res, next) => { .put((req, res, next) => {
try { apiValidator(schema.getValidationSchema('/settings/{settingID}', 'put'), req.body)
const payload = await apiValidator(getValidationSchema("/settings/{settingID}", "put"), req.body); .then((payload) => {
payload.id = req.params.setting_id; payload.id = req.params.setting_id;
const result = await internalSetting.update(res.locals.access, payload); return internalSetting.update(res.locals.access, payload);
res.status(200).send(result); })
} catch (err) { .then((result) => {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); res.status(200)
next(err); .send(result);
} })
.catch(next);
}); });
export default router; module.exports = router;

View File

@@ -1,18 +1,17 @@
import express from "express"; const express = require('express');
import internalToken from "../internal/token.js"; const jwtdecode = require('../lib/express/jwt-decode');
import jwtdecode from "../lib/express/jwt-decode.js"; const apiValidator = require('../lib/validator/api');
import apiValidator from "../lib/validator/api.js"; const internalToken = require('../internal/token');
import { express as logger } from "../logger.js"; const schema = require('../schema');
import { getValidationSchema } from "../schema/index.js";
const router = express.Router({ let router = express.Router({
caseSensitive: true, caseSensitive: true,
strict: true, strict: true,
mergeParams: true, mergeParams: true
}); });
router router
.route("/") .route('/')
.options((_, res) => { .options((_, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
@@ -24,17 +23,16 @@ router
* We also piggy back on to this method, allowing admins to get tokens * We also piggy back on to this method, allowing admins to get tokens
* for services like Job board and Worker. * for services like Job board and Worker.
*/ */
.get(jwtdecode(), async (req, res, next) => { .get(jwtdecode(), (req, res, next) => {
try { internalToken.getFreshToken(res.locals.access, {
const data = await internalToken.getFreshToken(res.locals.access, { expiry: (typeof req.query.expiry !== 'undefined' ? req.query.expiry : null),
expiry: typeof req.query.expiry !== "undefined" ? req.query.expiry : null, scope: (typeof req.query.scope !== 'undefined' ? req.query.scope : null)
scope: typeof req.query.scope !== "undefined" ? req.query.scope : null, })
}); .then((data) => {
res.status(200).send(data); res.status(200)
} catch (err) { .send(data);
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); })
next(err); .catch(next);
}
}) })
/** /**
@@ -43,14 +41,13 @@ router
* Create a new Token * Create a new Token
*/ */
.post(async (req, res, next) => { .post(async (req, res, next) => {
try { apiValidator(schema.getValidationSchema('/tokens', 'post'), req.body)
const data = await apiValidator(getValidationSchema("/tokens", "post"), req.body); .then(internalToken.getTokenFromEmail)
const result = await internalToken.getTokenFromEmail(data); .then((data) => {
res.status(200).send(result); res.status(200)
} catch (err) { .send(data);
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); })
next(err); .catch(next);
}
}); });
export default router; module.exports = router;

View File

@@ -1,27 +1,22 @@
import express from "express"; const express = require('express');
import internalUser from "../internal/user.js"; const validator = require('../lib/validator');
import Access from "../lib/access.js"; const jwtdecode = require('../lib/express/jwt-decode');
import { isCI } from "../lib/config.js"; const userIdFromMe = require('../lib/express/user-id-from-me');
import errs from "../lib/error.js"; const internalUser = require('../internal/user');
import jwtdecode from "../lib/express/jwt-decode.js"; const apiValidator = require('../lib/validator/api');
import userIdFromMe from "../lib/express/user-id-from-me.js"; const schema = require('../schema');
import apiValidator from "../lib/validator/api.js";
import validator from "../lib/validator/index.js";
import { express as logger } from "../logger.js";
import { getValidationSchema } from "../schema/index.js";
import { isSetup } from "../setup.js";
const router = express.Router({ let router = express.Router({
caseSensitive: true, caseSensitive: true,
strict: true, strict: true,
mergeParams: true, mergeParams: true
}); });
/** /**
* /api/users * /api/users
*/ */
router router
.route("/") .route('/')
.options((_, res) => { .options((_, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
@@ -32,38 +27,33 @@ router
* *
* Retrieve all users * Retrieve all users
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator({
const data = await validator(
{
additionalProperties: false, additionalProperties: false,
properties: { properties: {
expand: { expand: {
$ref: "common#/properties/expand", $ref: 'common#/properties/expand'
}, },
query: { query: {
$ref: "common#/properties/query", $ref: 'common#/properties/query'
},
},
},
{
expand:
typeof req.query.expand === "string"
? req.query.expand.split(",")
: null,
query: typeof req.query.query === "string" ? req.query.query : null,
},
);
const users = await internalUser.getAll(
res.locals.access,
data.expand,
data.query,
);
res.status(200).send(users);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
} }
}
}, {
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null),
query: (typeof req.query.query === 'string' ? req.query.query : null)
})
.then((data) => {
return internalUser.getAll(res.locals.access, data.expand, data.query);
})
.then((users) => {
res.status(200)
.send(users);
})
.catch((err) => {
console.log(err);
next(err);
});
//.catch(next);
}) })
/** /**
@@ -71,66 +61,16 @@ router
* *
* Create a new User * Create a new User
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
const body = req.body; apiValidator(schema.getValidationSchema('/users', 'post'), req.body)
.then((payload) => {
try { return internalUser.create(res.locals.access, payload);
// If we are in setup mode, we don't check access for current user
const setup = await isSetup();
if (!setup) {
logger.info("Creating a new user in setup mode");
const access = new Access(null);
await access.load(true);
res.locals.access = access;
// We are in setup mode, set some defaults for this first new user, such as making
// them an admin.
body.is_disabled = false;
if (typeof body.roles !== "object" || body.roles === null) {
body.roles = [];
}
if (body.roles.indexOf("admin") === -1) {
body.roles.push("admin");
}
}
const payload = await apiValidator(
getValidationSchema("/users", "post"),
body,
);
const user = await internalUser.create(res.locals.access, payload);
res.status(201).send(user);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}) })
.then((result) => {
/** res.status(201)
* DELETE /api/users .send(result);
* })
* Deletes ALL users. This is NOT GENERALLY AVAILABLE! .catch(next);
* (!) It is NOT an authenticated endpoint.
* (!) Only CI should be able to call this endpoint. As a result,
*
* it will only work when the env vars DEBUG=true and CI=true
*
* Do NOT set those env vars in a production environment!
*/
.delete(async (_, res, next) => {
if (isCI()) {
try {
logger.warn("Deleting all users - CI environment detected, allowing this operation");
await internalUser.deleteAll();
res.status(200).send(true);
} catch (err) {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
return;
}
next(new errs.ItemNotFoundError());
}); });
/** /**
@@ -139,7 +79,7 @@ router
* /api/users/123 * /api/users/123
*/ */
router router
.route("/:user_id") .route('/:user_id')
.options((_, res) => { .options((_, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
@@ -151,43 +91,37 @@ router
* *
* Retrieve a specific user * Retrieve a specific user
*/ */
.get(async (req, res, next) => { .get((req, res, next) => {
try { validator({
const data = await validator( required: ['user_id'],
{
required: ["user_id"],
additionalProperties: false, additionalProperties: false,
properties: { properties: {
user_id: { user_id: {
$ref: "common#/properties/id", $ref: 'common#/properties/id'
}, },
expand: { expand: {
$ref: "common#/properties/expand", $ref: 'common#/properties/expand'
}, }
}, }
}, }, {
{
user_id: req.params.user_id, user_id: req.params.user_id,
expand: expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null)
typeof req.query.expand === "string" })
? req.query.expand.split(",") .then((data) => {
: null, return internalUser.get(res.locals.access, {
},
);
const user = await internalUser.get(res.locals.access, {
id: data.user_id, id: data.user_id,
expand: data.expand, expand: data.expand,
omit: internalUser.getUserOmisionsByAccess( omit: internalUser.getUserOmisionsByAccess(res.locals.access, data.user_id)
res.locals.access,
data.user_id,
),
}); });
res.status(200).send(user); })
} catch (err) { .then((user) => {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); res.status(200)
.send(user);
})
.catch((err) => {
console.log(err);
next(err); next(err);
} });
}) })
/** /**
@@ -195,19 +129,17 @@ router
* *
* Update and existing user * Update and existing user
*/ */
.put(async (req, res, next) => { .put((req, res, next) => {
try { apiValidator(schema.getValidationSchema('/users/{userID}', 'put'), req.body)
const payload = await apiValidator( .then((payload) => {
getValidationSchema("/users/{userID}", "put"),
req.body,
);
payload.id = req.params.user_id; payload.id = req.params.user_id;
const result = await internalUser.update(res.locals.access, payload); return internalUser.update(res.locals.access, payload);
res.status(200).send(result); })
} catch (err) { .then((result) => {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); res.status(200)
next(err); .send(result);
} })
.catch(next);
}) })
/** /**
@@ -215,16 +147,13 @@ router
* *
* Update and existing user * Update and existing user
*/ */
.delete(async (req, res, next) => { .delete((req, res, next) => {
try { internalUser.delete(res.locals.access, {id: req.params.user_id})
const result = await internalUser.delete(res.locals.access, { .then((result) => {
id: req.params.user_id, res.status(200)
}); .send(result);
res.status(200).send(result); })
} catch (err) { .catch(next);
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
/** /**
@@ -233,8 +162,8 @@ router
* /api/users/123/auth * /api/users/123/auth
*/ */
router router
.route("/:user_id/auth") .route('/:user_id/auth')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())
@@ -245,19 +174,17 @@ router
* *
* Update password for a user * Update password for a user
*/ */
.put(async (req, res, next) => { .put((req, res, next) => {
try { apiValidator(schema.getValidationSchema('/users/{userID}/auth', 'put'), req.body)
const payload = await apiValidator( .then((payload) => {
getValidationSchema("/users/{userID}/auth", "put"),
req.body,
);
payload.id = req.params.user_id; payload.id = req.params.user_id;
const result = await internalUser.setPassword(res.locals.access, payload); return internalUser.setPassword(res.locals.access, payload);
res.status(200).send(result); })
} catch (err) { .then((result) => {
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); res.status(200)
next(err); .send(result);
} })
.catch(next);
}); });
/** /**
@@ -266,8 +193,8 @@ router
* /api/users/123/permissions * /api/users/123/permissions
*/ */
router router
.route("/:user_id/permissions") .route('/:user_id/permissions')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())
@@ -278,22 +205,17 @@ router
* *
* Set some or all permissions for a user * Set some or all permissions for a user
*/ */
.put(async (req, res, next) => { .put((req, res, next) => {
try { apiValidator(schema.getValidationSchema('/users/{userID}/permissions', 'put'), req.body)
const payload = await apiValidator( .then((payload) => {
getValidationSchema("/users/{userID}/permissions", "put"),
req.body,
);
payload.id = req.params.user_id; payload.id = req.params.user_id;
const result = await internalUser.setPermissions( return internalUser.setPermissions(res.locals.access, payload);
res.locals.access, })
payload, .then((result) => {
); res.status(200)
res.status(200).send(result); .send(result);
} catch (err) { })
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`); .catch(next);
next(err);
}
}); });
/** /**
@@ -302,7 +224,7 @@ router
* /api/users/123/login * /api/users/123/login
*/ */
router router
.route("/:user_id/login") .route('/:user_id/login')
.options((_, res) => { .options((_, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
@@ -313,16 +235,13 @@ router
* *
* Log in as a user * Log in as a user
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
try { internalUser.loginAs(res.locals.access, {id: parseInt(req.params.user_id, 10)})
const result = await internalUser.loginAs(res.locals.access, { .then((result) => {
id: Number.parseInt(req.params.user_id, 10), res.status(200)
}); .send(result);
res.status(200).send(result); })
} catch (err) { .catch(next);
logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
}); });
export default router; module.exports = router;

View File

@@ -110,11 +110,6 @@
"caching_enabled": { "caching_enabled": {
"description": "Should we cache assets", "description": "Should we cache assets",
"type": "boolean" "type": "boolean"
},
"email": {
"description": "Email address",
"type": "string",
"pattern": "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"
} }
} }
} }

View File

@@ -69,7 +69,7 @@
"type": "object" "type": "object"
}, },
"letsencrypt_email": { "letsencrypt_email": {
"$ref": "../common.json#/properties/email" "type": "string"
}, },
"propagation_seconds": { "propagation_seconds": {
"type": "integer", "type": "integer",

View File

@@ -9,11 +9,6 @@
"description": "Healthy", "description": "Healthy",
"example": "OK" "example": "OK"
}, },
"setup": {
"type": "boolean",
"description": "Whether the initial setup has been completed",
"example": true
},
"version": { "version": {
"type": "object", "type": "object",
"description": "The version object", "description": "The version object",

View File

@@ -54,63 +54,6 @@
"items": { "items": {
"type": "string" "type": "string"
} }
},
"permissions": {
"type": "object",
"description": "Permissions if expanded in request",
"required": [
"visibility",
"proxy_hosts",
"redirection_hosts",
"dead_hosts",
"streams",
"access_lists",
"certificates"
],
"properties": {
"visibility": {
"type": "string",
"description": "Visibility level",
"example": "all",
"pattern": "^(all|user)$"
},
"proxy_hosts": {
"type": "string",
"description": "Proxy Hosts access level",
"example": "all",
"pattern": "^(manage|view|hidden)$"
},
"redirection_hosts": {
"type": "string",
"description": "Redirection Hosts access level",
"example": "all",
"pattern": "^(manage|view|hidden)$"
},
"dead_hosts": {
"type": "string",
"description": "Dead Hosts access level",
"example": "all",
"pattern": "^(manage|view|hidden)$"
},
"streams": {
"type": "string",
"description": "Streams access level",
"example": "all",
"pattern": "^(manage|view|hidden)$"
},
"access_lists": {
"type": "string",
"description": "Access Lists access level",
"example": "all",
"pattern": "^(manage|view|hidden)$"
},
"certificates": {
"type": "string",
"description": "Certificates access level",
"example": "all",
"pattern": "^(manage|view|hidden)$"
}
}
} }
} }
} }

View File

@@ -1,24 +1,21 @@
import { dirname } from "node:path"; const refParser = require('@apidevtools/json-schema-ref-parser');
import { fileURLToPath } from "node:url";
import $RefParser from "@apidevtools/json-schema-ref-parser";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
let compiledSchema = null; let compiledSchema = null;
module.exports = {
/** /**
* Compiles the schema, by dereferencing it, only once * Compiles the schema, by dereferencing it, only once
* and returns the memory cached value * and returns the memory cached value
*/ */
const getCompiledSchema = async () => { getCompiledSchema: async () => {
if (compiledSchema === null) { if (compiledSchema === null) {
compiledSchema = await $RefParser.dereference(`${__dirname}/swagger.json`, { compiledSchema = await refParser.dereference(__dirname + '/swagger.json', {
mutateInputSchema: false, mutateInputSchema: false,
}); });
} }
return compiledSchema; return compiledSchema;
}; },
/** /**
* Scans the schema for the validation schema for the given path and method * Scans the schema for the validation schema for the given path and method
@@ -28,19 +25,17 @@ const getCompiledSchema = async () => {
* @param {string} method * @param {string} method
* @returns string|null * @returns string|null
*/ */
const getValidationSchema = (path, method) => { getValidationSchema: (path, method) => {
if ( if (compiledSchema !== null &&
compiledSchema !== null && typeof compiledSchema.paths[path] !== 'undefined' &&
typeof compiledSchema.paths[path] !== "undefined" && typeof compiledSchema.paths[path][method] !== 'undefined' &&
typeof compiledSchema.paths[path][method] !== "undefined" && typeof compiledSchema.paths[path][method].requestBody !== 'undefined' &&
typeof compiledSchema.paths[path][method].requestBody !== "undefined" && typeof compiledSchema.paths[path][method].requestBody.content !== 'undefined' &&
typeof compiledSchema.paths[path][method].requestBody.content !== "undefined" && typeof compiledSchema.paths[path][method].requestBody.content['application/json'] !== 'undefined' &&
typeof compiledSchema.paths[path][method].requestBody.content["application/json"] !== "undefined" && typeof compiledSchema.paths[path][method].requestBody.content['application/json'].schema !== 'undefined'
typeof compiledSchema.paths[path][method].requestBody.content["application/json"].schema !== "undefined"
) { ) {
return compiledSchema.paths[path][method].requestBody.content["application/json"].schema; return compiledSchema.paths[path][method].requestBody.content['application/json'].schema;
} }
return null; return null;
}
}; };
export { getCompiledSchema, getValidationSchema };

View File

@@ -11,7 +11,6 @@
"default": { "default": {
"value": { "value": {
"status": "OK", "status": "OK",
"setup": true,
"version": { "version": {
"major": 2, "major": 2,
"minor": 1, "minor": 1,

View File

@@ -10,10 +10,10 @@
// docker exec npm_core /command/s6-setuidgid 1000:1000 bash -c "/app/scripts/install-certbot-plugins" // docker exec npm_core /command/s6-setuidgid 1000:1000 bash -c "/app/scripts/install-certbot-plugins"
// //
import dnsPlugins from "../global/certbot-dns-plugins.json" with { type: "json" }; const dnsPlugins = require('../global/certbot-dns-plugins.json');
import { installPlugin } from "../lib/certbot.js"; const certbot = require('../lib/certbot');
import { certbot as logger } from "../logger.js"; const logger = require('../logger').certbot;
import batchflow from "batchflow"; const batchflow = require('batchflow');
let hasErrors = false; let hasErrors = false;
let failingPlugins = []; let failingPlugins = [];
@@ -25,7 +25,7 @@ if (process.argv.length > 2) {
batchflow(pluginKeys).sequential() batchflow(pluginKeys).sequential()
.each((i, pluginKey, next) => { .each((i, pluginKey, next) => {
installPlugin(pluginKey) certbot.installPlugin(pluginKey)
.then(() => { .then(() => {
next(); next();
}) })

View File

@@ -1,74 +1,72 @@
import { installPlugins } from "./lib/certbot.js"; const config = require('./lib/config');
import utils from "./lib/utils.js"; const logger = require('./logger').setup;
import { setup as logger } from "./logger.js"; const certificateModel = require('./models/certificate');
import authModel from "./models/auth.js"; const userModel = require('./models/user');
import certificateModel from "./models/certificate.js"; const userPermissionModel = require('./models/user_permission');
import settingModel from "./models/setting.js"; const utils = require('./lib/utils');
import userModel from "./models/user.js"; const authModel = require('./models/auth');
import userPermissionModel from "./models/user_permission.js"; const settingModel = require('./models/setting');
const certbot = require('./lib/certbot');
export const isSetup = async () => {
const row = await userModel.query().select("id").where("is_deleted", 0).first();
return row?.id > 0;
}
/** /**
* Creates a default admin users if one doesn't already exist in the database * Creates a default admin users if one doesn't already exist in the database
* *
* @returns {Promise} * @returns {Promise}
*/ */
const setupDefaultUser = async () => { const setupDefaultUser = () => {
const initialAdminEmail = process.env.INITIAL_ADMIN_EMAIL; return userModel
const initialAdminPassword = process.env.INITIAL_ADMIN_PASSWORD; .query()
.select('id', )
// This will only create a new user when there are no active users in the database .where('is_deleted', 0)
// and the INITIAL_ADMIN_EMAIL and INITIAL_ADMIN_PASSWORD environment variables are set. .first()
// Otherwise, users should be shown the setup wizard in the frontend. .then((row) => {
// I'm keeping this legacy behavior in case some people are automating deployments. if (!row || !row.id) {
if (!initialAdminEmail || !initialAdminPassword) {
return Promise.resolve();
}
const userIsetup = await isSetup();
if (!userIsetup) {
// Create a new user and set password // Create a new user and set password
logger.info(`Creating a new user: ${initialAdminEmail} with password: ${initialAdminPassword}`); const email = process.env.INITIAL_ADMIN_EMAIL || 'admin@example.com';
const password = process.env.INITIAL_ADMIN_PASSWORD || 'changeme';
logger.info('Creating a new user: ' + email + ' with password: ' + password);
const data = { const data = {
is_deleted: 0, is_deleted: 0,
email: email, email: email,
name: "Administrator", name: 'Administrator',
nickname: "Admin", nickname: 'Admin',
avatar: "", avatar: '',
roles: ["admin"], roles: ['admin'],
}; };
const user = await userModel return userModel
.query() .query()
.insertAndFetch(data); .insertAndFetch(data)
.then((user) => {
await authModel return authModel
.query() .query()
.insert({ .insert({
user_id: user.id, user_id: user.id,
type: "password", type: 'password',
secret: password, secret: password,
meta: {}, meta: {},
}); })
.then(() => {
await userPermissionModel.query().insert({ return userPermissionModel.query().insert({
user_id: user.id, user_id: user.id,
visibility: "all", visibility: 'all',
proxy_hosts: "manage", proxy_hosts: 'manage',
redirection_hosts: "manage", redirection_hosts: 'manage',
dead_hosts: "manage", dead_hosts: 'manage',
streams: "manage", streams: 'manage',
access_lists: "manage", access_lists: 'manage',
certificates: "manage", certificates: 'manage',
}); });
logger.info("Initial admin setup completed"); });
})
.then(() => {
logger.info('Initial admin setup completed');
});
} else if (config.debug()) {
logger.info('Admin user setup not required');
} }
});
}; };
/** /**
@@ -76,25 +74,31 @@ const setupDefaultUser = async () => {
* *
* @returns {Promise} * @returns {Promise}
*/ */
const setupDefaultSettings = async () => { const setupDefaultSettings = () => {
const row = await settingModel return settingModel
.query() .query()
.select("id") .select('id')
.where({ id: "default-site" }) .where({id: 'default-site'})
.first(); .first()
.then((row) => {
if (!row?.id) { if (!row || !row.id) {
await settingModel settingModel
.query() .query()
.insert({ .insert({
id: "default-site", id: 'default-site',
name: "Default Site", name: 'Default Site',
description: "What to show when Nginx is hit with an unknown Host", description: 'What to show when Nginx is hit with an unknown Host',
value: "congratulations", value: 'congratulations',
meta: {}, meta: {},
})
.then(() => {
logger.info('Default settings added');
}); });
logger.info("Default settings added");
} }
if (config.debug()) {
logger.info('Default setting setup not required');
}
});
}; };
/** /**
@@ -102,43 +106,45 @@ const setupDefaultSettings = async () => {
* *
* @returns {Promise} * @returns {Promise}
*/ */
const setupCertbotPlugins = async () => { const setupCertbotPlugins = () => {
const certificates = await certificateModel return certificateModel
.query() .query()
.where("is_deleted", 0) .where('is_deleted', 0)
.andWhere("provider", "letsencrypt"); .andWhere('provider', 'letsencrypt')
.then((certificates) => {
if (certificates && certificates.length) {
let plugins = [];
let promises = [];
if (certificates?.length) { certificates.map(function (certificate) {
const plugins = [];
const promises = [];
certificates.map((certificate) => {
if (certificate.meta && certificate.meta.dns_challenge === true) { if (certificate.meta && certificate.meta.dns_challenge === true) {
if (plugins.indexOf(certificate.meta.dns_provider) === -1) { if (plugins.indexOf(certificate.meta.dns_provider) === -1) {
plugins.push(certificate.meta.dns_provider); plugins.push(certificate.meta.dns_provider);
} }
// Make sure credentials file exists // Make sure credentials file exists
const credentials_loc = `/etc/letsencrypt/credentials/credentials-${certificate.id}`; const credentials_loc = '/etc/letsencrypt/credentials/credentials-' + certificate.id;
// Escape single quotes and backslashes // Escape single quotes and backslashes
const escapedCredentials = certificate.meta.dns_provider_credentials const escapedCredentials = certificate.meta.dns_provider_credentials.replaceAll('\'', '\\\'').replaceAll('\\', '\\\\');
.replaceAll("'", "\\'") const credentials_cmd = '[ -f \'' + credentials_loc + '\' ] || { mkdir -p /etc/letsencrypt/credentials 2> /dev/null; echo \'' + escapedCredentials + '\' > \'' + credentials_loc + '\' && chmod 600 \'' + credentials_loc + '\'; }';
.replaceAll("\\", "\\\\");
const credentials_cmd = `[ -f '${credentials_loc}' ] || { mkdir -p /etc/letsencrypt/credentials 2> /dev/null; echo '${escapedCredentials}' > '${credentials_loc}' && chmod 600 '${credentials_loc}'; }`;
promises.push(utils.exec(credentials_cmd)); promises.push(utils.exec(credentials_cmd));
} }
return true;
}); });
await installPlugins(plugins); return certbot.installPlugins(plugins)
.then(() => {
if (promises.length) { if (promises.length) {
await Promise.all(promises); return Promise.all(promises)
logger.info(`Added Certbot plugins ${plugins.join(", ")}`); .then(() => {
logger.info('Added Certbot plugins ' + plugins.join(', '));
});
} }
});
} }
});
}; };
/** /**
* Starts a timer to call run the logrotation binary every two days * Starts a timer to call run the logrotation binary every two days
* @returns {Promise} * @returns {Promise}
@@ -148,17 +154,20 @@ const setupLogrotation = () => {
const runLogrotate = async () => { const runLogrotate = async () => {
try { try {
await utils.exec("logrotate /etc/logrotate.d/nginx-proxy-manager"); await utils.exec('logrotate /etc/logrotate.d/nginx-proxy-manager');
logger.info("Logrotate completed."); logger.info('Logrotate completed.');
} catch (e) { } catch (e) { logger.warn(e); }
logger.warn(e);
}
}; };
logger.info("Logrotate Timer initialized"); logger.info('Logrotate Timer initialized');
setInterval(runLogrotate, intervalTimeout); setInterval(runLogrotate, intervalTimeout);
// And do this now as well // And do this now as well
return runLogrotate(); return runLogrotate();
}; };
export default () => setupDefaultUser().then(setupDefaultSettings).then(setupCertbotPlugins).then(setupLogrotation); module.exports = function () {
return setupDefaultUser()
.then(setupDefaultSettings)
.then(setupCertbotPlugins)
.then(setupLogrotation);
};

17
backend/validate-schema.js Executable file → Normal file
View File

@@ -1,19 +1,16 @@
#!/usr/bin/node const SwaggerParser = require('@apidevtools/swagger-parser');
const chalk = require('chalk');
import SwaggerParser from "@apidevtools/swagger-parser"; const schema = require('./schema');
import chalk from "chalk";
import { getCompiledSchema } from "./schema/index.js";
const log = console.log; const log = console.log;
getCompiledSchema().then(async (swaggerJSON) => { schema.getCompiledSchema().then(async (swaggerJSON) => {
try { try {
const api = await SwaggerParser.validate(swaggerJSON); const api = await SwaggerParser.validate(swaggerJSON);
console.log("API name: %s, Version: %s", api.info.title, api.info.version); console.log('API name: %s, Version: %s', api.info.title, api.info.version);
log(chalk.green(" Schema is valid")); log(chalk.green(' Schema is valid'));
} catch (e) { } catch (e) {
console.error(e); console.error(e);
log(chalk.red("", e.message), "\n"); log(chalk.red('', e.message), '\n');
process.exit(1); process.exit(1);
} }
}); });

File diff suppressed because it is too large Load Diff

View File

@@ -7,9 +7,7 @@ services:
fullstack: fullstack:
image: "${IMAGE}:${BRANCH_LOWER}-ci-${BUILD_NUMBER}" image: "${IMAGE}:${BRANCH_LOWER}-ci-${BUILD_NUMBER}"
environment: environment:
TZ: "${TZ:-Australia/Brisbane}"
DEBUG: 'true' DEBUG: 'true'
CI: 'true'
FORCE_COLOR: 1 FORCE_COLOR: 1
# Required for DNS Certificate provisioning in CI # Required for DNS Certificate provisioning in CI
LE_SERVER: 'https://ca.internal/acme/acme/directory' LE_SERVER: 'https://ca.internal/acme/acme/directory'

View File

@@ -18,7 +18,6 @@ services:
- website2.example.com - website2.example.com
- website3.example.com - website3.example.com
environment: environment:
TZ: "${TZ:-Australia/Brisbane}"
PUID: 1000 PUID: 1000
PGID: 1000 PGID: 1000
FORCE_COLOR: 1 FORCE_COLOR: 1
@@ -50,7 +49,6 @@ services:
- ../backend:/app - ../backend:/app
- ../frontend:/app/frontend - ../frontend:/app/frontend
- ../global:/app/global - ../global:/app/global
- '/etc/localtime:/etc/localtime:ro'
healthcheck: healthcheck:
test: ["CMD", "/usr/bin/check-health"] test: ["CMD", "/usr/bin/check-health"]
interval: 10s interval: 10s

View File

@@ -12,7 +12,6 @@ server {
location /api/ { location /api/ {
add_header X-Served-By $host; add_header X-Served-By $host;
proxy_http_version 1.1;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Forwarded-Scheme $scheme; proxy_set_header X-Forwarded-Scheme $scheme;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
@@ -24,14 +23,7 @@ server {
} }
location / { location / {
add_header X-Served-By $host; index index.html;
proxy_http_version 1.1; try_files $uri $uri.html $uri/ /index.html;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header X-Forwarded-Scheme $scheme;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_pass http://127.0.0.1:5173;
} }
} }

View File

@@ -15,7 +15,7 @@ if [ "$DEVELOPMENT" = 'true' ]; then
log_info 'Starting frontend ...' log_info 'Starting frontend ...'
s6-setuidgid "$PUID:$PGID" yarn install s6-setuidgid "$PUID:$PGID" yarn install
exec s6-setuidgid "$PUID:$PGID" yarn dev exec s6-setuidgid "$PUID:$PGID" yarn watch
else else
exit 0 exit 0
fi fi

View File

@@ -5,7 +5,7 @@
"preview": "vitepress preview" "preview": "vitepress preview"
}, },
"devDependencies": { "devDependencies": {
"vitepress": "^1.6.4" "vitepress": "^1.4.0"
}, },
"dependencies": {} "dependencies": {}
} }

View File

@@ -228,13 +228,3 @@ To enable the geoip2 module, you can create the custom configuration file `/data
load_module /usr/lib/nginx/modules/ngx_http_geoip2_module.so; load_module /usr/lib/nginx/modules/ngx_http_geoip2_module.so;
load_module /usr/lib/nginx/modules/ngx_stream_geoip2_module.so; load_module /usr/lib/nginx/modules/ngx_stream_geoip2_module.so;
``` ```
## Auto Initial User Creation
Setting these environment variables will create the default user on startup, skipping the UI first user setup screen:
```
environment:
INITIAL_ADMIN_EMAIL: my@example.com
INITIAL_ADMIN_PASSWORD: mypassword1
```

View File

@@ -23,10 +23,4 @@ Your best bet is to ask the [Reddit community for support](https://www.reddit.co
## When adding username and password access control to a proxy host, I can no longer login into the app. ## When adding username and password access control to a proxy host, I can no longer login into the app.
Having an Access Control List (ACL) with username and password requires the browser to always send this username Having an Access Control List (ACL) with username and password requires the browser to always send this username and password in the `Authorization` header on each request. If your proxied app also requires authentication (like Nginx Proxy Manager itself), most likely the app will also use the `Authorization` header to transmit this information, as this is the standardized header meant for this kind of information. However having multiples of the same headers is not allowed in the [internet standard](https://www.rfc-editor.org/rfc/rfc7230#section-3.2.2) and almost all apps do not support multiple values in the `Authorization` header. Hence one of the two logins will be broken. This can only be fixed by either removing one of the logins or by changing the app to use other non-standard headers for authorization.
and password in the `Authorization` header on each request. If your proxied app also requires authentication (like
Nginx Proxy Manager itself), most likely the app will also use the `Authorization` header to transmit this information,
as this is the standardized header meant for this kind of information. However having multiples of the same headers
is not allowed in the [internet standard](https://www.rfc-editor.org/rfc/rfc7230#section-3.2.2) and almost all apps
do not support multiple values in the `Authorization` header. Hence one of the two logins will be broken. This can
only be fixed by either removing one of the logins or by changing the app to use other non-standard headers for authorization.

Some files were not shown because too many files have changed in this diff Show More