update nginx/dep updates/fix eslint/change line endings

Signed-off-by: Zoey <zoey@z0ey.de>
This commit is contained in:
Zoey 2024-03-25 18:24:21 +01:00
parent ef5ac4cbd8
commit 906d7ce04a
No known key found for this signature in database
GPG key ID: 02A3919EB4F67328
96 changed files with 2579 additions and 2859 deletions

1
.gitattributes vendored Normal file
View file

@ -0,0 +1 @@
* text=auto

View file

@ -10,6 +10,7 @@ on:
- backend/** - backend/**
- global/** - global/**
- rootfs/** - rootfs/**
- src/**
pull_request: pull_request:
paths: paths:
- .github/workflows/docker.yml - .github/workflows/docker.yml
@ -18,6 +19,7 @@ on:
- backend/** - backend/**
- global/** - global/**
- rootfs/** - rootfs/**
- src/**
workflow_dispatch: workflow_dispatch:
jobs: jobs:
build: build:
@ -68,8 +70,6 @@ jobs:
tags: | tags: |
${{ steps.un.outputs.un }}/${{ steps.rn.outputs.rn }}:${{ github.ref_name }} ${{ steps.un.outputs.un }}/${{ steps.rn.outputs.rn }}:${{ github.ref_name }}
ghcr.io/${{ steps.un.outputs.un }}/${{ steps.rn.outputs.rn }}:${{ github.ref_name }} ghcr.io/${{ steps.un.outputs.un }}/${{ steps.rn.outputs.rn }}:${{ github.ref_name }}
build-args: |
"BUILD=${{ steps.rn.outputs.rn }}"
- name: show version - name: show version
if: ${{ github.event_name != 'pull_request' }} if: ${{ github.event_name != 'pull_request' }}
run: | run: |
@ -88,8 +88,6 @@ jobs:
platforms: linux/amd64,linux/arm64 #,linux/amd64/v2,linux/amd64/v3,linux/amd64/v4 #,linux/ppc64le,linux/s390x,linux/386,linux/arm/v7,linux/arm/v6 platforms: linux/amd64,linux/arm64 #,linux/amd64/v2,linux/amd64/v3,linux/amd64/v4 #,linux/ppc64le,linux/s390x,linux/386,linux/arm/v7,linux/arm/v6
push: ${{ github.event_name == 'pull_request' }} push: ${{ github.event_name == 'pull_request' }}
tags: ghcr.io/${{ steps.un.outputs.un }}/${{ steps.rn.outputs.rn }}:${{ steps.pr.outputs.pr }} tags: ghcr.io/${{ steps.un.outputs.un }}/${{ steps.rn.outputs.rn }}:${{ steps.pr.outputs.pr }}
build-args: |
"BUILD=${{ steps.rn.outputs.rn }}"
- name: show version (PR) - name: show version (PR)
if: ${{ github.event_name == 'pull_request' }} if: ${{ github.event_name == 'pull_request' }}
run: docker run --rm --entrypoint nginx ghcr.io/${{ steps.un.outputs.un }}/${{ steps.rn.outputs.rn }}:${{ steps.pr.outputs.pr }} -V run: docker run --rm --entrypoint nginx ghcr.io/${{ steps.un.outputs.un }}/${{ steps.rn.outputs.rn }}:${{ steps.pr.outputs.pr }} -V

View file

@ -18,9 +18,10 @@ jobs:
run: | run: |
DOCKERFILES="$(find . -name "*Dockerfile*")" DOCKERFILES="$(find . -name "*Dockerfile*")"
for file in $(echo "$DOCKERFILES" | tr " " "\n"); do for file in $(echo "$DOCKERFILES" | tr " " "\n"); do
# DL3003 warning: Use WORKDIR to switch to a directory
# DL3018 warning: Pin versions in apk add. Instead of `apk add <package>` use `apk add <package>=<version>` # DL3018 warning: Pin versions in apk add. Instead of `apk add <package>` use `apk add <package>=<version>`
# DL3013 warning: Pin versions in pip. Instead of `pip install <package>` use `pip install <package>==<version>` or `pip install --requirement <requirements file>` # DL3013 warning: Pin versions in pip. Instead of `pip install <package>` use `pip install <package>==<version>` or `pip install --requirement <requirements file>`
hadolint "$file" --ignore DL3013 --ignore DL3018 | tee -a hadolint.log hadolint "$file" --ignore DL3003 --ignore DL3013 --ignore DL3018 | tee -a hadolint.log
done done
if grep -q "DL[0-9]\+\|SC[0-9]\+" hadolint.log; then if grep -q "DL[0-9]\+\|SC[0-9]\+" hadolint.log; then
exit 1 exit 1

View file

@ -11,4 +11,4 @@ jobs:
- name: json-syntax-check - name: json-syntax-check
uses: limitusus/json-syntax-check@v2 uses: limitusus/json-syntax-check@v2
with: with:
pattern: "\\.json$*" pattern: "\\.json"

View file

@ -15,4 +15,4 @@ jobs:
with: with:
check_filenames: true check_filenames: true
check_hidden: true check_hidden: true
skip: .gitignore,block-exploits.conf,showdown.min.js,jquery.min.js,xregexp-all.js skip: .git,.gitignore,showdown.min.js,jquery.min.js,xregexp-all.js

View file

@ -1,6 +1,8 @@
name: update-and-lint name: update-and-lint
on: on:
push: push:
branches:
- develop
schedule: schedule:
- cron: "0 */6 * * *" - cron: "0 */6 * * *"
workflow_dispatch: workflow_dispatch:
@ -13,7 +15,7 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 19 node-version: 21
- name: eslint - name: eslint
run: | run: |
cd backend cd backend
@ -23,9 +25,9 @@ jobs:
run: | run: |
curl -L https://unpkg.com/xregexp/xregexp-all.js -o rootfs/nftd/xregexp-all.js curl -L https://unpkg.com/xregexp/xregexp-all.js -o rootfs/nftd/xregexp-all.js
curl -L https://unpkg.com/showdown/dist/showdown.min.js -o rootfs/nftd/showdown.min.js curl -L https://unpkg.com/showdown/dist/showdown.min.js -o rootfs/nftd/showdown.min.js
curl -L https://code.jquery.com/jquery-"$(git ls-remote --tags https://github.com/jquery/jquery | cut -d/ -f3 | sort -V | tail -1 | sed -E "s/\^\{\}//")".min.js -o rootfs/nftd/jquery.min.js curl -L https://code.jquery.com/jquery-"$(git ls-remote --tags https://github.com/jquery/jquery | cut -d/ -f3 | sort -V | tail -1)".min.js -o rootfs/nftd/jquery.min.js
curl -L https://cdn.jsdelivr.net/npm/bootstrap@"$(git ls-remote --tags https://github.com/twbs/bootstrap v3.3.* | cut -d/ -f3 | sort -V | tail -1 | sed -E "s/\^\{\}//")"/dist/css/bootstrap.min.css -o rootfs/html/404/bootstrap.min.css curl -L https://cdn.jsdelivr.net/npm/bootstrap@"$(git ls-remote --tags https://github.com/twbs/bootstrap v3.3.* | cut -d/ -f3 | sort -V | tail -1)"/dist/css/bootstrap.min.css -o rootfs/html/404/bootstrap.min.css
curl -L https://cdn.jsdelivr.net/npm/bootstrap@"$(git ls-remote --tags https://github.com/twbs/bootstrap v3.3.* | cut -d/ -f3 | sort -V | tail -1 | sed -E "s/\^\{\}//")"/dist/css/bootstrap.min.css -o rootfs/html/default/bootstrap.min.css curl -L https://cdn.jsdelivr.net/npm/bootstrap@"$(git ls-remote --tags https://github.com/twbs/bootstrap v3.3.* | cut -d/ -f3 | sort -V | tail -1)"/dist/css/bootstrap.min.css -o rootfs/html/default/bootstrap.min.css
- name: nginxbeautifier - name: nginxbeautifier
run: | run: |
yarn global add nginxbeautifier yarn global add nginxbeautifier

View file

@ -1,20 +0,0 @@
name: yq
on:
workflow_dispatch:
jobs:
yq:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.YQ }}
- name: update workflows
run: for workflow in .github/workflows/*.yml; do yq "$workflow" | tee "$workflow".tmp && mv "$workflow".tmp "$workflow"; done
- name: push changes
run: |
git config user.name "GitHub"
git config user.email "noreply@github.com"
git add -A
git diff-index --quiet HEAD || git commit -sm "yq"
git push

View file

@ -56,14 +56,14 @@ RUN apk upgrade --no-cache -a && \
echo "APPSEC_FAILURE_ACTION=deny" | tee -a /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf && \ echo "APPSEC_FAILURE_ACTION=deny" | tee -a /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf && \
sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf sed -i "s|BOUNCING_ON_TYPE=all|BOUNCING_ON_TYPE=ban|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf
FROM zoeyvid/nginx-quic:262 FROM zoeyvid/nginx-quic:271
SHELL ["/bin/ash", "-eo", "pipefail", "-c"] SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
ARG CRS_VER=v4.1.0 ARG CRS_VER=v4.1.0
COPY rootfs / COPY rootfs /
COPY --from=zoeyvid/certbot-docker:27 /usr/local /usr/local COPY --from=zoeyvid/certbot-docker:34 /usr/local /usr/local
COPY --from=zoeyvid/curl-quic:376 /usr/local/bin/curl /usr/local/bin/curl COPY --from=zoeyvid/curl-quic:380 /usr/local/bin/curl /usr/local/bin/curl
RUN apk upgrade --no-cache -a && \ RUN apk upgrade --no-cache -a && \
apk add --no-cache ca-certificates tzdata tini \ apk add --no-cache ca-certificates tzdata tini \
@ -145,3 +145,7 @@ ENV PUID=0 \
WORKDIR /app WORKDIR /app
ENTRYPOINT ["tini", "--", "entrypoint.sh"] ENTRYPOINT ["tini", "--", "entrypoint.sh"]
HEALTHCHECK CMD healthcheck.sh HEALTHCHECK CMD healthcheck.sh
EXPOSE 80/tcp
EXPOSE 81/tcp
EXPOSE 443/tcp
EXPOSE 443/udp

View file

@ -1,73 +0,0 @@
{
"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
}
]
}
}

7
backend/.prettierrc Normal file
View file

@ -0,0 +1,7 @@
{
"semi": true,
"useTabs": true,
"printWidth": 1000,
"singleQuote": true,
"bracketSameLine": true
}

View file

@ -1,9 +1,9 @@
const express = require('express'); const express = require('express');
const bodyParser = require('body-parser'); const bodyParser = require('body-parser');
const fileUpload = require('express-fileupload'); const fileUpload = require('express-fileupload');
const compression = require('compression'); const compression = require('compression');
const config = require('./lib/config'); const config = require('./lib/config');
const log = require('./logger').express; const log = require('./logger').express;
/** /**
* App * App
@ -11,7 +11,7 @@ const log = require('./logger').express;
const app = express(); const app = express();
app.use(fileUpload()); app.use(fileUpload());
app.use(bodyParser.json()); app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true})); app.use(bodyParser.urlencoded({ extended: true }));
// Gzip // Gzip
app.use(compression()); app.use(compression());
@ -41,12 +41,12 @@ app.use(function (req, res, next) {
} }
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();
}); });
@ -58,18 +58,17 @@ app.use('/', require('./routes/api/main'));
// no stacktraces leaked to user // no stacktraces leaked to user
// eslint-disable-next-line // eslint-disable-next-line
app.use(function (err, req, res, next) { app.use(function (err, req, res, next) {
const payload = {
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 (config.debug() || (req.baseUrl + req.path).includes('nginx/certificates')) { if (config.debug() || (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,
}; };
} }
@ -77,14 +76,12 @@ app.use(function (err, req, res, next) {
if (typeof err.stack !== 'undefined' && err.stack) { if (typeof err.stack !== 'undefined' && err.stack) {
if (config.debug()) { if (config.debug()) {
log.debug(err.stack); log.debug(err.stack);
} else if (typeof err.public == 'undefined' || !err.public) { } else if (typeof err.public === 'undefined' || !err.public) {
log.warn(err.message); log.warn(err.message);
} }
} }
res res.status(err.status || 500).send(payload);
.status(err.status || 500)
.send(payload);
}); });
module.exports = app; module.exports = app;

View file

@ -10,18 +10,18 @@ function generateDbConfig() {
return cfg.knex; return cfg.knex;
} }
return { return {
client: cfg.engine, client: cfg.engine,
connection: { connection: {
host: cfg.host, host: cfg.host,
user: cfg.user, user: cfg.user,
password: cfg.password, password: cfg.password,
database: cfg.name, database: cfg.name,
port: cfg.port, port: cfg.port,
ssl: cfg.tls, ssl: cfg.tls,
}, },
migrations: { migrations: {
tableName: 'migrations' tableName: 'migrations',
} },
}; };
} }

View file

@ -0,0 +1,5 @@
import globals from 'globals';
import pluginJs from '@eslint/js';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
export default [{ files: ['**/*.js'], languageOptions: { sourceType: 'commonjs' } }, { languageOptions: { globals: globals.node } }, pluginJs.configs.recommended, eslintPluginPrettierRecommended];

View file

@ -2,16 +2,17 @@
const logger = require('./logger').global; const logger = require('./logger').global;
async function appStart () { async function appStart() {
const migrate = require('./migrate'); const migrate = require('./migrate');
const setup = require('./setup'); const setup = require('./setup');
const app = require('./app'); const app = require('./app');
const apiValidator = require('./lib/validator/api'); const apiValidator = require('./lib/validator/api');
const internalNginx = require('./internal/nginx'); const internalNginx = require('./internal/nginx');
const internalCertificate = require('./internal/certificate'); const internalCertificate = require('./internal/certificate');
const internalIpRanges = require('./internal/ip_ranges'); const internalIpRanges = require('./internal/ip_ranges');
return migrate.latest() return migrate
.latest()
.then(setup) .then(setup)
.then(() => { .then(() => {
return apiValidator.loadSchemas; return apiValidator.loadSchemas;

View file

@ -1,67 +1,65 @@
const _ = require('lodash'); const _ = require('lodash');
const fs = require('fs'); const fs = require('fs');
const batchflow = require('batchflow'); const batchflow = require('batchflow');
const logger = require('../logger').access; const logger = require('../logger').access;
const error = require('../lib/error'); const error = require('../lib/error');
const utils = require('../lib/utils'); const utils = require('../lib/utils');
const accessListModel = require('../models/access_list'); const accessListModel = require('../models/access_list');
const accessListAuthModel = require('../models/access_list_auth'); const accessListAuthModel = require('../models/access_list_auth');
const accessListClientModel = require('../models/access_list_client'); const accessListClientModel = require('../models/access_list_client');
const proxyHostModel = require('../models/proxy_host'); const proxyHostModel = require('../models/proxy_host');
const internalAuditLog = require('./audit-log'); const internalAuditLog = require('./audit-log');
const internalNginx = require('./nginx'); const internalNginx = require('./nginx');
function 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: (access, data) => { create: (access, data) => {
return access.can('access_lists:create', data) return access
.then((/*access_data*/) => { .can('access_lists:create', data)
.then((/* access_data */) => {
return accessListModel 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) => { .then((row) => {
data.id = row.id; data.id = row.id;
let promises = []; const promises = [];
// Now add the items // Now add the items
data.items.map((item) => { data.items.map((item) => {
promises.push(accessListAuthModel promises.push(
.query() accessListAuthModel.query().insert({
.insert({
access_list_id: row.id, access_list_id: row.id,
username: item.username, username: item.username,
password: item.password password: item.password,
}) }),
); );
}); });
// Now add the clients // Now add the clients
if (typeof data.clients !== 'undefined' && data.clients) { if (typeof data.clients !== 'undefined' && data.clients) {
data.clients.map((client) => { data.clients.map((client) => {
promises.push(accessListClientModel promises.push(
.query() accessListClientModel.query().insert({
.insert({
access_list_id: row.id, access_list_id: row.id,
address: client.address, address: client.address,
directive: client.directive directive: client.directive,
}) }),
); );
}); });
} }
@ -70,16 +68,21 @@ const internalAccessList = {
}) })
.then(() => { .then(() => {
// re-fetch with expansions // re-fetch with expansions
return internalAccessList.get(access, { return internalAccessList.get(
id: data.id, access,
expand: ['owner', 'items', 'clients', 'proxy_hosts.access_list.[clients,items]'] {
}, true /* <- skip masking */); id: data.id,
expand: ['owner', 'items', 'clients', 'proxy_hosts.access_list.[clients,items]'],
},
true /* <- skip masking */,
);
}) })
.then((row) => { .then((row) => {
// Audit log // Audit log
data.meta = _.assign({}, data.meta || {}, row.meta); data.meta = _.assign({}, data.meta || {}, row.meta);
return internalAccessList.build(row) return internalAccessList
.build(row)
.then(() => { .then(() => {
if (row.proxy_host_count) { if (row.proxy_host_count) {
return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts); return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
@ -88,10 +91,10 @@ const internalAccessList = {
.then(() => { .then(() => {
// Add to audit log // Add to audit log
return internalAuditLog.add(access, { return internalAuditLog.add(access, {
action: 'created', action: 'created',
object_type: 'access-list', object_type: 'access-list',
object_id: row.id, object_id: row.id,
meta: internalAccessList.maskItems(data) meta: internalAccessList.maskItems(data),
}); });
}) })
.then(() => { .then(() => {
@ -109,9 +112,10 @@ const internalAccessList = {
* @return {Promise} * @return {Promise}
*/ */
update: (access, data) => { update: (access, data) => {
return access.can('access_lists:update', data.id) return access
.then((/*access_data*/) => { .can('access_lists:update', data.id)
return internalAccessList.get(access, {id: data.id}); .then((/* access_data */) => {
return internalAccessList.get(access, { id: data.id });
}) })
.then((row) => { .then((row) => {
if (row.id !== data.id) { if (row.id !== data.id) {
@ -122,31 +126,27 @@ const internalAccessList = {
.then(() => { .then(() => {
// patch name if specified // patch name if specified
if (typeof data.name !== 'undefined' && data.name) { if (typeof data.name !== 'undefined' && data.name) {
return accessListModel return accessListModel.query().where({ id: data.id }).patch({
.query() name: data.name,
.where({id: data.id}) satisfy_any: data.satisfy_any,
.patch({ pass_auth: data.pass_auth,
name: data.name, });
satisfy_any: data.satisfy_any,
pass_auth: data.pass_auth,
});
} }
}) })
.then(() => { .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) {
let promises = []; const promises = [];
let items_to_keep = []; const items_to_keep = [];
data.items.map(function (item) { data.items.map(function (item) {
if (item.password) { if (item.password) {
promises.push(accessListAuthModel promises.push(
.query() accessListAuthModel.query().insert({
.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
@ -154,79 +154,76 @@ const internalAccessList = {
} }
}); });
let query = accessListAuthModel const query = accessListAuthModel.query().delete().where('access_list_id', data.id);
.query()
.delete()
.where('access_list_id', data.id);
if (items_to_keep.length) { if (items_to_keep.length) {
query.andWhere('username', 'NOT IN', items_to_keep); query.andWhere('username', 'NOT IN', items_to_keep);
} }
return query return query.then(() => {
.then(() => { // Add new items
// Add new items if (promises.length) {
if (promises.length) { return Promise.all(promises);
return Promise.all(promises); }
} });
});
} }
}) })
.then(() => { .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) {
let promises = []; const promises = [];
data.clients.map(function (client) { data.clients.map(function (client) {
if (client.address) { if (client.address) {
promises.push(accessListClientModel promises.push(
.query() accessListClientModel.query().insert({
.insert({
access_list_id: data.id, access_list_id: data.id,
address: client.address, address: client.address,
directive: client.directive directive: client.directive,
}) }),
); );
} }
}); });
let query = accessListClientModel const query = accessListClientModel.query().delete().where('access_list_id', data.id);
.query()
.delete()
.where('access_list_id', data.id);
return query return query.then(() => {
.then(() => { // Add new items
// Add new items if (promises.length) {
if (promises.length) { return Promise.all(promises);
return Promise.all(promises); }
} });
});
} }
}) })
.then(() => { .then(() => {
// Add to audit log // Add to audit log
return 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(() => { .then(() => {
// re-fetch with expansions // re-fetch with expansions
return internalAccessList.get(access, { return internalAccessList.get(
id: data.id, access,
expand: ['owner', 'items', 'clients', 'proxy_hosts.[certificate,access_list.[clients,items]]'] {
}, true /* <- skip masking */); id: data.id,
expand: ['owner', 'items', 'clients', 'proxy_hosts.[certificate,access_list.[clients,items]]'],
},
true /* <- skip masking */,
);
}) })
.then((row) => { .then((row) => {
return internalAccessList.build(row) return internalAccessList
.build(row)
.then(() => { .then(() => {
if (row.proxy_host_count) { if (row.proxy_host_count) {
return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts); return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
} }
}).then(internalNginx.reload) })
.then(internalNginx.reload)
.then(() => { .then(() => {
return internalAccessList.maskItems(row); return internalAccessList.maskItems(row);
}); });
@ -247,16 +244,10 @@ const internalAccessList = {
data = {}; data = {};
} }
return access.can('access_lists:get', data.id) return access
.can('access_lists:get', data.id)
.then((access_data) => { .then((access_data) => {
let query = accessListModel const query = accessListModel.query().select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count')).joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0').where('access_list.is_deleted', 0).andWhere('access_list.id', data.id).allowGraph('[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]').first();
.query()
.select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count'))
.joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0')
.where('access_list.is_deleted', 0)
.andWhere('access_list.id', data.id)
.allowGraph('[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]')
.first();
if (access_data.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));
@ -291,9 +282,10 @@ const internalAccessList = {
* @returns {Promise} * @returns {Promise}
*/ */
delete: (access, data) => { delete: (access, data) => {
return access.can('access_lists:delete', data.id) return access
.can('access_lists:delete', data.id)
.then(() => { .then(() => {
return internalAccessList.get(access, {id: data.id, expand: ['proxy_hosts', 'items', 'clients']}); return internalAccessList.get(access, { id: data.id, expand: ['proxy_hosts', 'items', 'clients'] });
}) })
.then((row) => { .then((row) => {
if (!row) { if (!row) {
@ -310,7 +302,7 @@ const internalAccessList = {
.query() .query()
.where('id', row.id) .where('id', row.id)
.patch({ .patch({
is_deleted: 1 is_deleted: 1,
}) })
.then(() => { .then(() => {
// 2. update any proxy hosts that were using it (ignoring permissions) // 2. update any proxy hosts that were using it (ignoring permissions)
@ -318,7 +310,7 @@ const internalAccessList = {
return 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(() => { .then(() => {
// 3. reconfigure those hosts, then reload nginx // 3. reconfigure those hosts, then reload nginx
@ -336,21 +328,21 @@ const internalAccessList = {
}) })
.then(() => { .then(() => {
// delete the htpasswd file // delete the htpasswd file
let htpasswd_file = internalAccessList.getFilename(row); const htpasswd_file = internalAccessList.getFilename(row);
try { try {
fs.unlinkSync(htpasswd_file); fs.unlinkSync(htpasswd_file);
} catch (err) { } catch {
// do nothing // do nothing
} }
}) })
.then(() => { .then(() => {
// 4. audit log // 4. audit log
return 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']),
}); });
}); });
}) })
@ -368,16 +360,10 @@ const internalAccessList = {
* @returns {Promise} * @returns {Promise}
*/ */
getAll: (access, expand, search_query) => { getAll: (access, expand, search_query) => {
return access.can('access_lists:list') return access
.can('access_lists:list')
.then((access_data) => { .then((access_data) => {
let query = accessListModel const query = accessListModel.query().select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count')).joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0').where('access_list.is_deleted', 0).groupBy('access_list.id').allowGraph('[owner,items,clients]').orderBy('access_list.name', 'ASC');
.query()
.select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count'))
.joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0')
.where('access_list.is_deleted', 0)
.groupBy('access_list.id')
.allowGraph('[owner,items,clients]')
.orderBy('access_list.name', 'ASC');
if (access_data.permission_visibility !== 'all') { 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));
@ -417,19 +403,15 @@ const internalAccessList = {
* @returns {Promise} * @returns {Promise}
*/ */
getCount: (user_id, visibility) => { getCount: (user_id, visibility) => {
let query = accessListModel const query = accessListModel.query().count('id as count').where('is_deleted', 0);
.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() return query.first().then((row) => {
.then((row) => { return parseInt(row.count, 10);
return parseInt(row.count, 10); });
});
}, },
/** /**
@ -447,7 +429,7 @@ const internalAccessList = {
first_char = val.password.charAt(0); first_char = val.password.charAt(0);
} }
list.items[idx].hint = first_char + ('*').repeat(repeat_for); list.items[idx].hint = first_char + '*'.repeat(repeat_for);
list.items[idx].password = ''; list.items[idx].password = '';
}); });
} }
@ -475,54 +457,55 @@ const internalAccessList = {
logger.info('Building Access file #' + list.id + ' for: ' + list.name); logger.info('Building Access file #' + list.id + ' for: ' + list.name);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let htpasswd_file = internalAccessList.getFilename(list); const htpasswd_file = internalAccessList.getFilename(list);
// 1. remove any existing access file // 1. remove any existing access file
try { try {
fs.unlinkSync(htpasswd_file); fs.unlinkSync(htpasswd_file);
} catch (err) { } catch {
// do nothing // do nothing
} }
// 2. create empty access file // 2. create empty access file
try { try {
fs.writeFileSync(htpasswd_file, '', {encoding: 'utf8'}); fs.writeFileSync(htpasswd_file, '', { encoding: 'utf8' });
resolve(htpasswd_file); resolve(htpasswd_file);
} catch (err) { } catch (err) {
reject(err); reject(err);
} }
}) }).then((htpasswd_file) => {
.then((htpasswd_file) => { // 3. generate password for each user
// 3. generate password for each user if (list.items.length) {
if (list.items.length) { return new Promise((resolve, reject) => {
return new Promise((resolve, reject) => { batchflow(list.items)
batchflow(list.items).sequential() .sequential()
.each((i, item, next) => { .each((i, item, next) => {
if (typeof item.password !== 'undefined' && item.password.length) { if (typeof item.password !== 'undefined' && item.password.length) {
logger.info('Adding: ' + item.username); logger.info('Adding: ' + item.username);
utils.execFile('htpasswd', ['-b', htpasswd_file, item.username, item.password]) utils
.then((/*result*/) => { .execFile('htpasswd', ['-b', htpasswd_file, item.username, item.password])
next(); .then((/* result */) => {
}) next();
.catch((err) => { })
logger.error(err); .catch((err) => {
next(err); logger.error(err);
}); next(err);
} });
}) }
.error((err) => { })
logger.error(err); .error((err) => {
reject(err); logger.error(err);
}) reject(err);
.end((results) => { })
logger.success('Built Access file #' + list.id + ' for: ' + list.name); .end((results) => {
resolve(results); logger.success('Built Access file #' + list.id + ' for: ' + list.name);
}); resolve(results);
}); });
} });
}); }
} });
},
}; };
module.exports = internalAccessList; module.exports = internalAccessList;

View file

@ -1,8 +1,7 @@
const error = require('../lib/error'); const error = require('../lib/error');
const auditLogModel = require('../models/audit-log'); const auditLogModel = require('../models/audit-log');
const internalAuditLog = { const internalAuditLog = {
/** /**
* All logs * All logs
* *
@ -12,28 +11,22 @@ const internalAuditLog = {
* @returns {Promise} * @returns {Promise}
*/ */
getAll: (access, expand, search_query) => { getAll: (access, expand, search_query) => {
return access.can('auditlog:list') return access.can('auditlog:list').then(() => {
.then(() => { const query = auditLogModel.query().orderBy('created_on', 'DESC').orderBy('id', 'DESC').limit(100).allowGraph('[user]');
let query = auditLogModel
.query()
.orderBy('created_on', 'DESC')
.orderBy('id', 'DESC')
.limit(100)
.allowGraph('[user]');
// 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('meta', 'like', '%' + search_query + '%'); this.where('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 query; return query;
}); });
}, },
/** /**
@ -61,18 +54,18 @@ const internalAuditLog = {
reject(new error.InternalValidationError('Audit log entry must contain an Action')); reject(new error.InternalValidationError('Audit log entry must contain an Action'));
} else { } 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
resolve(auditLogModel resolve(
.query() auditLogModel.query().insert({
.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 || {},
})); }),
);
} }
}); });
} },
}; };
module.exports = internalAuditLog; module.exports = internalAuditLog;

View file

@ -1,21 +1,21 @@
const _ = require('lodash'); const _ = require('lodash');
const fs = require('fs'); const fs = require('fs');
const https = require('https'); const https = require('https');
const moment = require('moment'); const moment = require('moment');
const logger = require('../logger').ssl; const logger = require('../logger').ssl;
const error = require('../lib/error'); const error = require('../lib/error');
const utils = require('../lib/utils'); const utils = require('../lib/utils');
const certificateModel = require('../models/certificate'); const certificateModel = require('../models/certificate');
const dnsPlugins = require('../certbot-dns-plugins.json'); const dnsPlugins = require('../certbot-dns-plugins.json');
const internalAuditLog = require('./audit-log'); const internalAuditLog = require('./audit-log');
const internalNginx = require('./nginx'); const internalNginx = require('./nginx');
const certbot = require('../lib/certbot'); const certbot = require('../lib/certbot');
const archiver = require('archiver'); const archiver = require('archiver');
const crypto = require('crypto'); const crypto = require('crypto');
const path = require('path'); const path = require('path');
const { isArray } = require('lodash'); const { isArray } = require('lodash');
const certbotConfig = '/data/tls/certbot/config.ini'; const certbotConfig = '/data/tls/certbot/config.ini';
const certbotCommand = 'certbot --logs-dir /tmp/certbot-log --work-dir /tmp/certbot-work --config-dir /data/tls/certbot'; const certbotCommand = 'certbot --logs-dir /tmp/certbot-log --work-dir /tmp/certbot-work --config-dir /data/tls/certbot';
function omissions() { function omissions() {
@ -23,10 +23,9 @@ function omissions() {
} }
const internalCertificate = { const internalCertificate = {
allowedSslFiles: ['certificate', 'certificate_key', 'intermediate_certificate'],
allowedSslFiles: ['certificate', 'certificate_key', 'intermediate_certificate'], intervalTimeout: 1000 * 60 * 60 * Number(process.env.CRT),
intervalTimeout: 1000 * 60 * 60 * Number(process.env.CRT), interval: null,
interval: null,
intervalProcessing: false, intervalProcessing: false,
initTimer: () => { initTimer: () => {
@ -42,22 +41,19 @@ const internalCertificate = {
internalCertificate.intervalProcessing = true; internalCertificate.intervalProcessing = true;
logger.info('Renewing TLS certs close to expiry...'); logger.info('Renewing TLS certs close to expiry...');
const cmd = certbotCommand + ' renew --quiet ' + const cmd = certbotCommand + ' renew --quiet ' + '--config "' + certbotConfig + '" ' + '--preferred-challenges "dns,http" ' + '--no-random-sleep-on-renew';
'--config "' + certbotConfig + '" ' +
'--preferred-challenges "dns,http" ' +
'--no-random-sleep-on-renew';
return utils.exec(cmd) return utils
.exec(cmd)
.then((result) => { .then((result) => {
if (result) { if (result) {
logger.info('Renew Result: ' + result); logger.info('Renew Result: ' + result);
} }
return internalNginx.reload() return internalNginx.reload().then(() => {
.then(() => { logger.info('Renew Complete');
logger.info('Renew Complete'); return result;
return result; });
});
}) })
.then(() => { .then(() => {
// Now go and fetch all the certbot certs from the db and query the files and update expiry times // Now go and fetch all the certbot certs from the db and query the files and update expiry times
@ -67,24 +63,25 @@ const internalCertificate = {
.andWhere('provider', 'letsencrypt') .andWhere('provider', 'letsencrypt')
.then((certificates) => { .then((certificates) => {
if (certificates && certificates.length) { if (certificates && certificates.length) {
let promises = []; const promises = [];
certificates.map(function (certificate) { certificates.map(function (certificate) {
promises.push( promises.push(
internalCertificate.getCertificateInfoFromFile('/data/tls/certbot/live/npm-' + certificate.id + '/fullchain.pem') internalCertificate
.getCertificateInfoFromFile('/data/tls/certbot/live/npm-' + certificate.id + '/fullchain.pem')
.then((cert_info) => { .then((cert_info) => {
return certificateModel return certificateModel
.query() .query()
.where('id', certificate.id) .where('id', certificate.id)
.andWhere('provider', 'letsencrypt') .andWhere('provider', 'letsencrypt')
.patch({ .patch({
expires_on: moment(cert_info.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss') expires_on: moment(cert_info.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss'),
}); });
}) })
.catch((err) => { .catch((err) => {
// Don't want to stop the train here, just log the error // Don't want to stop the train here, just log the error
logger.error(err.message); logger.error(err.message);
}) }),
); );
}); });
@ -108,7 +105,8 @@ const internalCertificate = {
* @returns {Promise} * @returns {Promise}
*/ */
create: (access, data) => { create: (access, data) => {
return access.can('certificates:create', data) return access
.can('certificates:create', data)
.then(() => { .then(() => {
data.owner_user_id = access.token.getUserId(1); data.owner_user_id = access.token.getUserId(1);
@ -116,30 +114,29 @@ const internalCertificate = {
data.nice_name = data.domain_names.join(', '); data.nice_name = data.domain_names.join(', ');
} }
return certificateModel return certificateModel.query().insertAndFetch(data).then(utils.omitRow(omissions()));
.query()
.insertAndFetch(data)
.then(utils.omitRow(omissions()));
}) })
.then((certificate) => { .then((certificate) => {
if (certificate.provider === 'letsencrypt') { if (certificate.provider === 'letsencrypt') {
// Request a new Cert using Certbot. Let the fun begin. // Request a new Cert using Certbot. Let the fun begin.
if (certificate.meta.dns_challenge) { if (certificate.meta.dns_challenge) {
return internalCertificate.requestLetsEncryptSslWithDnsChallenge(certificate) return internalCertificate
.requestLetsEncryptSslWithDnsChallenge(certificate)
.then(() => { .then(() => {
return certificate; return certificate;
}) })
.catch((err) => { .catch((err) => {
// In the event of failure, throw err back // In the event of failure, throw err back
throw err; throw err;
}); });
} else { } else {
return internalCertificate.requestLetsEncryptSsl(certificate) return internalCertificate
.requestLetsEncryptSsl(certificate)
.then(() => { .then(() => {
return certificate; return certificate;
}) })
.catch((err) => { .catch((err) => {
// In the event of failure, throw err back // In the event of failure, throw err back
throw err; throw err;
}); });
} }
@ -152,16 +149,12 @@ const internalCertificate = {
// At this point, the certbot cert should exist on disk. // At this point, the certbot cert should exist on disk.
// Lets get the expiry date from the file and update the row silently // Lets get the expiry date from the file and update the row silently
return internalCertificate return internalCertificate
.getCertificateInfoFromFile( .getCertificateInfoFromFile('/data/tls/certbot/live/npm-' + certificate.id + '/fullchain.pem')
'/data/tls/certbot/live/npm-' + certificate.id + '/fullchain.pem'
)
.then((cert_info) => { .then((cert_info) => {
return certificateModel return certificateModel
.query() .query()
.patchAndFetchById(certificate.id, { .patchAndFetchById(certificate.id, {
expires_on: moment(cert_info.dates.to, 'X').format( expires_on: moment(cert_info.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss'),
'YYYY-MM-DD HH:mm:ss'
),
}) })
.then((saved_row) => { .then((saved_row) => {
// Add cert data for audit log // Add cert data for audit log
@ -171,7 +164,8 @@ const internalCertificate = {
return saved_row; return saved_row;
}); });
}).catch(async (error) => { })
.catch(async (error) => {
// Delete the certificate from the database if it was not created successfully // Delete the certificate from the database if it was not created successfully
await certificateModel.query().deleteById(certificate.id); await certificateModel.query().deleteById(certificate.id);
@ -182,23 +176,22 @@ const internalCertificate = {
} }
}) })
.then((certificate) => { .then((certificate) => {
data.meta = _.assign({}, data.meta || {}, certificate.meta); data.meta = _.assign({}, data.meta || {}, certificate.meta);
// Add to audit log // Add to audit log
return internalAuditLog.add(access, { return internalAuditLog
action: 'created', .add(access, {
object_type: 'certificate', action: 'created',
object_id: certificate.id, object_type: 'certificate',
meta: data object_id: certificate.id,
}) meta: data,
})
.then(() => { .then(() => {
return certificate; return certificate;
}); });
}); });
}, },
/** /**
* @param {Access} access * @param {Access} access
* @param {Object} data * @param {Object} data
@ -208,9 +201,10 @@ const internalCertificate = {
* @return {Promise} * @return {Promise}
*/ */
update: (access, data) => { update: (access, data) => {
return access.can('certificates:update', data.id) return access
.then((/*access_data*/) => { .can('certificates:update', data.id)
return internalCertificate.get(access, {id: data.id}); .then((/* access_data */) => {
return internalCertificate.get(access, { id: data.id });
}) })
.then((row) => { .then((row) => {
if (row.id !== data.id) { if (row.id !== data.id) {
@ -224,7 +218,7 @@ const internalCertificate = {
.then(utils.omitRow(omissions())) .then(utils.omitRow(omissions()))
.then((saved_row) => { .then((saved_row) => {
saved_row.meta = internalCertificate.cleanMeta(saved_row.meta); saved_row.meta = internalCertificate.cleanMeta(saved_row.meta);
data.meta = internalCertificate.cleanMeta(data.meta); data.meta = internalCertificate.cleanMeta(data.meta);
// Add row.nice_name for custom certs // Add row.nice_name for custom certs
if (saved_row.provider === 'other') { if (saved_row.provider === 'other') {
@ -232,12 +226,13 @@ const internalCertificate = {
} }
// Add to audit log // Add to audit log
return internalAuditLog.add(access, { return internalAuditLog
action: 'updated', .add(access, {
object_type: 'certificate', action: 'updated',
object_id: row.id, object_type: 'certificate',
meta: _.omit(data, ['expires_on']) // this prevents json circular reference because expires_on might be raw object_id: row.id,
}) meta: _.omit(data, ['expires_on']), // this prevents json circular reference because expires_on might be raw
})
.then(() => { .then(() => {
return saved_row; return saved_row;
}); });
@ -258,14 +253,10 @@ const internalCertificate = {
data = {}; data = {};
} }
return access.can('certificates:get', data.id) return access
.can('certificates:get', data.id)
.then((access_data) => { .then((access_data) => {
let query = certificateModel const query = certificateModel.query().where('is_deleted', 0).andWhere('id', data.id).allowGraph('[owner]').first();
.query()
.where('is_deleted', 0)
.andWhere('id', data.id)
.allowGraph('[owner]')
.first();
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));
@ -297,7 +288,8 @@ const internalCertificate = {
*/ */
download: (access, data) => { download: (access, data) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
access.can('certificates:get', data) access
.can('certificates:get', data)
.then(() => { .then(() => {
return internalCertificate.get(access, data); return internalCertificate.get(access, data);
}) })
@ -309,45 +301,46 @@ const internalCertificate = {
throw new error.ItemNotFoundError('Certificate ' + certificate.nice_name + ' does not exists'); throw new error.ItemNotFoundError('Certificate ' + certificate.nice_name + ' does not exists');
} }
let certFiles = fs.readdirSync(zipDirectory) const certFiles = fs
.readdirSync(zipDirectory)
.filter((fn) => fn.endsWith('.pem')) .filter((fn) => fn.endsWith('.pem'))
.map((fn) => fs.realpathSync(path.join(zipDirectory, fn))); .map((fn) => fs.realpathSync(path.join(zipDirectory, fn)));
const downloadName = 'npm-' + data.id + '-' + `${Date.now()}.zip`; const downloadName = 'npm-' + data.id + '-' + `${Date.now()}.zip`;
const opName = '/tmp/' + downloadName; const opName = '/tmp/' + downloadName;
internalCertificate.zipFiles(certFiles, opName) internalCertificate
.zipFiles(certFiles, opName)
.then(() => { .then(() => {
logger.debug('zip completed : ', opName); logger.debug('zip completed : ', opName);
const resp = { const resp = {
fileName: opName fileName: opName,
}; };
resolve(resp); resolve(resp);
}).catch((err) => reject(err)); })
.catch((err) => reject(err));
} else { } else {
throw new error.ValidationError('Only Certbot certificates can be downloaded'); throw new error.ValidationError('Only Certbot certificates can be downloaded');
} }
}).catch((err) => reject(err)); })
.catch((err) => reject(err));
}); });
}, },
/** /**
* @param {String} source * @param {String} source
* @param {String} out * @param {String} out
* @returns {Promise} * @returns {Promise}
*/ */
zipFiles(source, out) { zipFiles(source, out) {
const archive = archiver('zip', { zlib: { level: 9 } }); const archive = archiver('zip', { zlib: { level: 9 } });
const stream = fs.createWriteStream(out); const stream = fs.createWriteStream(out);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
source source.map((fl) => {
.map((fl) => { const fileName = path.basename(fl);
let fileName = path.basename(fl); logger.debug(fl, 'added to certificate zip');
logger.debug(fl, 'added to certificate zip'); archive.file(fl, { name: fileName });
archive.file(fl, { name: fileName }); });
}); archive.on('error', (err) => reject(err)).pipe(stream);
archive
.on('error', (err) => reject(err))
.pipe(stream);
stream.on('close', () => resolve()); stream.on('close', () => resolve());
archive.finalize(); archive.finalize();
@ -362,9 +355,10 @@ const internalCertificate = {
* @returns {Promise} * @returns {Promise}
*/ */
delete: (access, data) => { delete: (access, data) => {
return access.can('certificates:delete', data.id) return access
.can('certificates:delete', data.id)
.then(() => { .then(() => {
return internalCertificate.get(access, {id: data.id}); return internalCertificate.get(access, { id: data.id });
}) })
.then((row) => { .then((row) => {
if (!row) { if (!row) {
@ -375,17 +369,17 @@ const internalCertificate = {
.query() .query()
.where('id', row.id) .where('id', row.id)
.patch({ .patch({
is_deleted: 1 is_deleted: 1,
}) })
.then(() => { .then(() => {
// Add to audit log // Add to audit log
row.meta = internalCertificate.cleanMeta(row.meta); row.meta = internalCertificate.cleanMeta(row.meta);
return internalAuditLog.add(access, { return internalAuditLog.add(access, {
action: 'deleted', action: 'deleted',
object_type: 'certificate', object_type: 'certificate',
object_id: row.id, object_id: row.id,
meta: _.omit(row, omissions()) meta: _.omit(row, omissions()),
}); });
}) })
.then(() => { .then(() => {
@ -409,32 +403,26 @@ const internalCertificate = {
* @returns {Promise} * @returns {Promise}
*/ */
getAll: (access, expand, search_query) => { getAll: (access, expand, search_query) => {
return access.can('certificates:list') return access.can('certificates:list').then((access_data) => {
.then((access_data) => { const query = certificateModel.query().where('is_deleted', 0).groupBy('id').allowGraph('[owner]').orderBy('nice_name', 'ASC');
let query = certificateModel
.query()
.where('is_deleted', 0)
.groupBy('id')
.allowGraph('[owner]')
.orderBy('nice_name', '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') { if (typeof search_query === 'string') {
query.where(function () { query.where(function () {
this.where('nice_name', 'like', '%' + search_query + '%'); this.where('nice_name', '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()));
}); });
}, },
/** /**
@ -445,19 +433,15 @@ const internalCertificate = {
* @returns {Promise} * @returns {Promise}
*/ */
getCount: (user_id, visibility) => { getCount: (user_id, visibility) => {
let query = certificateModel const query = certificateModel.query().count('id as count').where('is_deleted', 0);
.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() return query.first().then((row) => {
.then((row) => { return parseInt(row.count, 10);
return parseInt(row.count, 10); });
});
}, },
/** /**
@ -504,18 +488,17 @@ const internalCertificate = {
resolve(); resolve();
} }
}); });
}) }).then(() => {
.then(() => { return new Promise((resolve, reject) => {
return new Promise((resolve, reject) => { fs.writeFile(dir + '/privkey.pem', certificate.meta.certificate_key, function (err) {
fs.writeFile(dir + '/privkey.pem', certificate.meta.certificate_key, function (err) { if (err) {
if (err) { reject(err);
reject(err); } else {
} else { resolve();
resolve(); }
}
});
}); });
}); });
});
}, },
/** /**
@ -528,9 +511,9 @@ const internalCertificate = {
*/ */
createQuickCertificate: (access, data) => { createQuickCertificate: (access, data) => {
return internalCertificate.create(access, { return internalCertificate.create(access, {
provider: 'letsencrypt', provider: 'letsencrypt',
domain_names: data.domain_names, domain_names: data.domain_names,
meta: data.meta meta: data.meta,
}); });
}, },
@ -545,7 +528,7 @@ const internalCertificate = {
validate: (data) => { validate: (data) => {
return new Promise((resolve) => { return new Promise((resolve) => {
// Put file contents into an object // Put file contents into an object
let files = {}; const files = {};
_.map(data.files, (file, name) => { _.map(data.files, (file, name) => {
if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) { if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) {
files[name] = file.data.toString(); files[name] = file.data.toString();
@ -553,13 +536,13 @@ const internalCertificate = {
}); });
resolve(files); resolve(files);
}) }).then((files) => {
.then((files) => { // For each file, create a temp file and write the contents to it
// For each file, create a temp file and write the contents to it // Then test it depending on the file type
// Then test it depending on the file type const promises = [];
let promises = []; _.map(files, (content, type) => {
_.map(files, (content, type) => { promises.push(
promises.push(new Promise((resolve) => { new Promise((resolve) => {
if (type === 'certificate_key') { if (type === 'certificate_key') {
resolve(internalCertificate.checkPrivateKey(content)); resolve(internalCertificate.checkPrivateKey(content));
} else { } else {
@ -567,21 +550,21 @@ const internalCertificate = {
resolve(internalCertificate.getCertificateInfo(content, true)); resolve(internalCertificate.getCertificateInfo(content, true));
} }
}).then((res) => { }).then((res) => {
return {[type]: res}; return { [type]: res };
})); }),
);
});
return Promise.all(promises).then((files) => {
let data = {};
_.each(files, (file) => {
data = _.assign({}, data, file);
}); });
return Promise.all(promises) return data;
.then((files) => {
let data = {};
_.each(files, (file) => {
data = _.assign({}, data, file);
});
return data;
});
}); });
});
}, },
/** /**
@ -592,40 +575,41 @@ const internalCertificate = {
* @returns {Promise} * @returns {Promise}
*/ */
upload: (access, data) => { upload: (access, data) => {
return internalCertificate.get(access, {id: data.id}) return internalCertificate.get(access, { id: data.id }).then((row) => {
.then((row) => { if (row.provider !== 'other') {
if (row.provider !== 'other') { throw new error.ValidationError('Cannot upload certificates for this type of provider');
throw new error.ValidationError('Cannot upload certificates for this type of provider'); }
}
return internalCertificate.validate(data) return internalCertificate
.then((validations) => { .validate(data)
if (typeof validations.certificate === 'undefined') { .then((validations) => {
throw new error.ValidationError('Certificate file was not provided'); if (typeof validations.certificate === 'undefined') {
throw new error.ValidationError('Certificate file was not provided');
}
_.map(data.files, (file, name) => {
if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) {
row.meta[name] = file.data.toString();
} }
_.map(data.files, (file, name) => {
if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) {
row.meta[name] = file.data.toString();
}
});
// TODO: This uses a mysql only raw function that won't translate to postgres
return internalCertificate.update(access, {
id: data.id,
expires_on: moment(validations.certificate.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss'),
domain_names: [validations.certificate.cn],
meta: _.clone(row.meta) // Prevent the update method from changing this value that we'll use later
})
.then((certificate) => {
certificate.meta = row.meta;
return internalCertificate.writeCustomCert(certificate);
});
})
.then(() => {
return _.pick(row.meta, internalCertificate.allowedSslFiles);
}); });
});
// TODO: This uses a mysql only raw function that won't translate to postgres
return internalCertificate
.update(access, {
id: data.id,
expires_on: moment(validations.certificate.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss'),
domain_names: [validations.certificate.cn],
meta: _.clone(row.meta), // Prevent the update method from changing this value that we'll use later
})
.then((certificate) => {
certificate.meta = row.meta;
return internalCertificate.writeCustomCert(certificate);
});
})
.then(() => {
return _.pick(row.meta, internalCertificate.allowedSslFiles);
});
});
}, },
/** /**
@ -636,7 +620,7 @@ const internalCertificate = {
*/ */
checkPrivateKey: (private_key) => { checkPrivateKey: (private_key) => {
const randomName = crypto.randomBytes(8).toString('hex'); const randomName = crypto.randomBytes(8).toString('hex');
const filepath = path.join('/tmp', 'certificate_' + randomName); const filepath = path.join('/tmp', 'certificate_' + randomName);
fs.writeFileSync(filepath, private_key); fs.writeFileSync(filepath, private_key);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const failTimeout = setTimeout(() => { const failTimeout = setTimeout(() => {
@ -669,13 +653,15 @@ const internalCertificate = {
*/ */
getCertificateInfo: (certificate, throw_expired) => { getCertificateInfo: (certificate, throw_expired) => {
const randomName = crypto.randomBytes(8).toString('hex'); const randomName = crypto.randomBytes(8).toString('hex');
const filepath = path.join('/tmp', 'certificate_' + randomName); const filepath = path.join('/tmp', 'certificate_' + randomName);
fs.writeFileSync(filepath, certificate); fs.writeFileSync(filepath, certificate);
return internalCertificate.getCertificateInfoFromFile(filepath, throw_expired) return internalCertificate
.getCertificateInfoFromFile(filepath, throw_expired)
.then((certData) => { .then((certData) => {
fs.unlinkSync(filepath); fs.unlinkSync(filepath);
return certData; return certData;
}).catch((err) => { })
.catch((err) => {
fs.unlinkSync(filepath); fs.unlinkSync(filepath);
throw err; throw err;
}); });
@ -689,9 +675,10 @@ const internalCertificate = {
* @param {Boolean} [throw_expired] Throw when the certificate is out of date * @param {Boolean} [throw_expired] Throw when the certificate is out of date
*/ */
getCertificateInfoFromFile: (certificate_file, throw_expired) => { getCertificateInfoFromFile: (certificate_file, throw_expired) => {
let certData = {}; const certData = {};
return utils.exec('openssl x509 -in ' + certificate_file + ' -subject -noout') return utils
.exec('openssl x509 -in ' + certificate_file + ' -subject -noout')
.then((result) => { .then((result) => {
// subject=CN = something.example.com // subject=CN = something.example.com
const regex = /(?:subject=)?[^=]+=\s+(\S+)/gim; const regex = /(?:subject=)?[^=]+=\s+(\S+)/gim;
@ -701,7 +688,7 @@ const internalCertificate = {
throw new error.ValidationError('Could not determine subject from certificate: ' + result); throw new error.ValidationError('Could not determine subject from certificate: ' + result);
} }
certData['cn'] = match[1]; certData.cn = match[1];
}) })
.then(() => { .then(() => {
return utils.exec('openssl x509 -in ' + certificate_file + ' -issuer -noout'); return utils.exec('openssl x509 -in ' + certificate_file + ' -issuer -noout');
@ -714,7 +701,7 @@ const internalCertificate = {
throw new error.ValidationError('Could not determine issuer from certificate: ' + result); throw new error.ValidationError('Could not determine issuer from certificate: ' + result);
} }
certData['issuer'] = match[1]; certData.issuer = match[1];
}) })
.then(() => { .then(() => {
return utils.exec('openssl x509 -in ' + certificate_file + ' -dates -noout'); return utils.exec('openssl x509 -in ' + certificate_file + ' -dates -noout');
@ -723,7 +710,7 @@ const internalCertificate = {
// notBefore=Jul 14 04:04:29 2018 GMT // notBefore=Jul 14 04:04:29 2018 GMT
// notAfter=Oct 12 04:04:29 2018 GMT // notAfter=Oct 12 04:04:29 2018 GMT
let validFrom = null; let validFrom = null;
let validTo = null; let validTo = null;
const lines = result.split('\n'); const lines = result.split('\n');
lines.map(function (str) { lines.map(function (str) {
@ -749,13 +736,14 @@ const internalCertificate = {
throw new error.ValidationError('Certificate has expired'); throw new error.ValidationError('Certificate has expired');
} }
certData['dates'] = { certData.dates = {
from: validFrom, from: validFrom,
to: validTo to: validTo,
}; };
return certData; return certData;
}).catch((err) => { })
.catch((err) => {
throw new error.ValidationError('Certificate is not valid (' + err.message + ')', err); throw new error.ValidationError('Certificate is not valid (' + err.message + ')', err);
}); });
}, },
@ -789,12 +777,7 @@ const internalCertificate = {
requestLetsEncryptSsl: (certificate) => { requestLetsEncryptSsl: (certificate) => {
logger.info('Requesting Certbot certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', ')); logger.info('Requesting Certbot certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
let cmd = certbotCommand + ' certonly ' + let cmd = certbotCommand + ' certonly ' + '--config "' + certbotConfig + '" ' + '--cert-name "npm-' + certificate.id + '" ' + '--authenticator webroot ' + '--preferred-challenges "dns,http" ' + '--domains "' + certificate.domain_names.join(',') + '"';
'--config "' + certbotConfig + '" ' +
'--cert-name "npm-' + certificate.id + '" ' +
'--authenticator webroot ' +
'--preferred-challenges "dns,http" ' +
'--domains "' + certificate.domain_names.join(',') + '"';
if (certificate.meta.letsencrypt_email === '') { if (certificate.meta.letsencrypt_email === '') {
cmd = cmd + ' --register-unsafely-without-email '; cmd = cmd + ' --register-unsafely-without-email ';
@ -804,11 +787,10 @@ const internalCertificate = {
logger.info('Command:', cmd); logger.info('Command:', cmd);
return utils.exec(cmd) return utils.exec(cmd).then((result) => {
.then((result) => { logger.success(result);
logger.success(result); return result;
return result; });
});
}, },
/** /**
@ -825,20 +807,10 @@ const internalCertificate = {
const credentialsLocation = '/data/tls/certbot/credentials/credentials-' + certificate.id; const credentialsLocation = '/data/tls/certbot/credentials/credentials-' + certificate.id;
// Escape single quotes and backslashes // Escape single quotes and backslashes
const escapedCredentials = certificate.meta.dns_provider_credentials.replaceAll('\'', '\\\'').replaceAll('\\', '\\\\'); const escapedCredentials = certificate.meta.dns_provider_credentials.replaceAll("'", "\\'").replaceAll('\\', '\\\\');
const credentialsCmd = `echo '${escapedCredentials}' | tee '${credentialsLocation}'`; const credentialsCmd = `echo '${escapedCredentials}' | tee '${credentialsLocation}'`;
let mainCmd = certbotCommand + ' certonly ' + let mainCmd = certbotCommand + ' certonly ' + '--config "' + certbotConfig + '" ' + '--cert-name "npm-' + certificate.id + '" ' + '--domains "' + certificate.domain_names.join(',') + '" ' + '--authenticator ' + dnsPlugin.full_plugin_name + ' ' + '--' + dnsPlugin.full_plugin_name + '-credentials "' + credentialsLocation + '"' + (certificate.meta.propagation_seconds !== undefined ? ' --' + dnsPlugin.full_plugin_name + '-propagation-seconds ' + certificate.meta.propagation_seconds : '');
'--config "' + certbotConfig + '" ' +
'--cert-name "npm-' + certificate.id + '" ' +
'--domains "' + certificate.domain_names.join(',') + '" ' +
'--authenticator ' + dnsPlugin.full_plugin_name + ' ' +
'--' + dnsPlugin.full_plugin_name + '-credentials "' + credentialsLocation + '"' +
(
certificate.meta.propagation_seconds !== undefined
? ' --' + dnsPlugin.full_plugin_name + '-propagation-seconds ' + certificate.meta.propagation_seconds
: ''
);
if (certificate.meta.letsencrypt_email === '') { if (certificate.meta.letsencrypt_email === '') {
mainCmd = mainCmd + ' --register-unsafely-without-email '; mainCmd = mainCmd + ' --register-unsafely-without-email ';
@ -861,7 +833,6 @@ const internalCertificate = {
} }
}, },
/** /**
* @param {Access} access * @param {Access} access
* @param {Object} data * @param {Object} data
@ -869,7 +840,8 @@ const internalCertificate = {
* @returns {Promise} * @returns {Promise}
*/ */
renew: (access, data) => { renew: (access, data) => {
return access.can('certificates:update', data) return access
.can('certificates:update', data)
.then(() => { .then(() => {
return internalCertificate.get(access, data); return internalCertificate.get(access, data);
}) })
@ -882,20 +854,19 @@ const internalCertificate = {
return internalCertificate.getCertificateInfoFromFile('/data/tls/certbot/live/npm-' + certificate.id + '/fullchain.pem'); return internalCertificate.getCertificateInfoFromFile('/data/tls/certbot/live/npm-' + certificate.id + '/fullchain.pem');
}) })
.then((cert_info) => { .then((cert_info) => {
return certificateModel return certificateModel.query().patchAndFetchById(certificate.id, {
.query() expires_on: moment(cert_info.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss'),
.patchAndFetchById(certificate.id, { });
expires_on: moment(cert_info.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss')
});
}) })
.then((updated_certificate) => { .then((updated_certificate) => {
// Add to audit log // Add to audit log
return internalAuditLog.add(access, { return internalAuditLog
action: 'renewed', .add(access, {
object_type: 'certificate', action: 'renewed',
object_id: updated_certificate.id, object_type: 'certificate',
meta: updated_certificate object_id: updated_certificate.id,
}) meta: updated_certificate,
})
.then(() => { .then(() => {
return updated_certificate; return updated_certificate;
}); });
@ -913,19 +884,14 @@ const internalCertificate = {
renewLetsEncryptSsl: (certificate) => { renewLetsEncryptSsl: (certificate) => {
logger.info('Renewing Certbot certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', ')); logger.info('Renewing Certbot certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
const cmd = certbotCommand + ' renew --force-renewal ' + const cmd = certbotCommand + ' renew --force-renewal ' + '--config "' + certbotConfig + '" ' + '--cert-name "npm-' + certificate.id + '" ' + '--preferred-challenges "dns,http" ' + '--no-random-sleep-on-renew';
'--config "' + certbotConfig + '" ' +
'--cert-name "npm-' + certificate.id + '" ' +
'--preferred-challenges "dns,http" ' +
'--no-random-sleep-on-renew';
logger.info('Command:', cmd); logger.info('Command:', cmd);
return utils.exec(cmd) return utils.exec(cmd).then((result) => {
.then((result) => { logger.info(result);
logger.info(result); return result;
return result; });
});
}, },
/** /**
@ -941,19 +907,14 @@ const internalCertificate = {
logger.info(`Renewing Certbot certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); logger.info(`Renewing Certbot certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);
let mainCmd = certbotCommand + ' renew --force-renewal ' + const mainCmd = certbotCommand + ' renew --force-renewal ' + '--config "' + certbotConfig + '" ' + '--cert-name "npm-' + certificate.id + '" ' + '--preferred-challenges "dns,http" ' + '--no-random-sleep-on-renew';
'--config "' + certbotConfig + '" ' +
'--cert-name "npm-' + certificate.id + '" ' +
'--preferred-challenges "dns,http" ' +
'--no-random-sleep-on-renew';
logger.info('Command:', mainCmd); logger.info('Command:', mainCmd);
return utils.exec(mainCmd) return utils.exec(mainCmd).then(async (result) => {
.then(async (result) => { logger.info(result);
logger.info(result); return result;
return result; });
});
}, },
/** /**
@ -964,18 +925,15 @@ const internalCertificate = {
revokeLetsEncryptSsl: (certificate, throw_errors) => { revokeLetsEncryptSsl: (certificate, throw_errors) => {
logger.info('Revoking Certbot certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', ')); logger.info('Revoking Certbot certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
const mainCmd = certbotCommand + ' revoke ' + const mainCmd = certbotCommand + ' revoke ' + '--config "' + certbotConfig + '" ' + '--cert-path "/data/tls/certbot/live/npm-' + certificate.id + '/privkey.pem" ' + '--cert-path "/data/tls/certbot/live/npm-' + certificate.id + '/fullchain.pem" ' + '--delete-after-revoke';
'--config "' + certbotConfig + '" ' +
'--cert-path "/data/tls/certbot/live/npm-' + certificate.id + '/privkey.pem" ' +
'--cert-path "/data/tls/certbot/live/npm-' + certificate.id + '/fullchain.pem" ' +
'--delete-after-revoke';
// Don't fail command if file does not exist // Don't fail command if file does not exist
const delete_credentialsCmd = `rm -f '/data/tls/certbot/credentials/credentials-${certificate.id}' || true`; const delete_credentialsCmd = `rm -f '/data/tls/certbot/credentials/credentials-${certificate.id}' || true`;
logger.info('Command:', mainCmd + '; ' + delete_credentialsCmd); logger.info('Command:', mainCmd + '; ' + delete_credentialsCmd);
return utils.exec(mainCmd) return utils
.exec(mainCmd)
.then(async (result) => { .then(async (result) => {
await utils.exec(delete_credentialsCmd); await utils.exec(delete_credentialsCmd);
logger.info(result); logger.info(result);
@ -1009,7 +967,7 @@ const internalCertificate = {
*/ */
disableInUseHosts: (in_use_result) => { disableInUseHosts: (in_use_result) => {
if (in_use_result.total_count) { if (in_use_result.total_count) {
let promises = []; const promises = [];
if (in_use_result.proxy_hosts.length) { if (in_use_result.proxy_hosts.length) {
promises.push(internalNginx.bulkDeleteConfigs('proxy_host', in_use_result.proxy_hosts)); promises.push(internalNginx.bulkDeleteConfigs('proxy_host', in_use_result.proxy_hosts));
@ -1024,7 +982,6 @@ const internalCertificate = {
} }
return Promise.all(promises); return Promise.all(promises);
} else { } else {
return Promise.resolve(); return Promise.resolve();
} }
@ -1039,7 +996,7 @@ const internalCertificate = {
*/ */
enableInUseHosts: (in_use_result) => { enableInUseHosts: (in_use_result) => {
if (in_use_result.total_count) { if (in_use_result.total_count) {
let promises = []; const promises = [];
if (in_use_result.proxy_hosts.length) { if (in_use_result.proxy_hosts.length) {
promises.push(internalNginx.bulkGenerateConfigs('proxy_host', in_use_result.proxy_hosts)); promises.push(internalNginx.bulkGenerateConfigs('proxy_host', in_use_result.proxy_hosts));
@ -1054,7 +1011,6 @@ const internalCertificate = {
} }
return Promise.all(promises); return Promise.all(promises);
} else { } else {
return Promise.resolve(); return Promise.resolve();
} }
@ -1071,32 +1027,31 @@ const internalCertificate = {
} }
// Create a test challenge file // Create a test challenge file
const testChallengeDir = '/tmp/acme-challenge/.well-known/acme-challenge'; const testChallengeDir = '/tmp/acme-challenge/.well-known/acme-challenge';
const testChallengeFile = testChallengeDir + '/test-challenge'; const testChallengeFile = testChallengeDir + '/test-challenge';
fs.mkdirSync(testChallengeDir, {recursive: true}); fs.mkdirSync(testChallengeDir, { recursive: true });
fs.writeFileSync(testChallengeFile, 'Success', {encoding: 'utf8'}); fs.writeFileSync(testChallengeFile, 'Success', { encoding: 'utf8' });
async function performTestForDomain (domain) { async function performTestForDomain(domain) {
logger.info('Testing http challenge for ' + domain); logger.info('Testing http challenge for ' + domain);
const url = `http://${domain}/.well-known/acme-challenge/test-challenge`; const url = `http://${domain}/.well-known/acme-challenge/test-challenge`;
const formBody = `method=G&url=${encodeURI(url)}&bodytype=T&locationid=10`; const formBody = `method=G&url=${encodeURI(url)}&bodytype=T&locationid=10`;
const options = { const options = {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(formBody), 'Content-Length': Buffer.byteLength(formBody),
'Connection': 'keep-alive', Connection: 'keep-alive',
'User-Agent': 'NPMplus', 'User-Agent': 'NPMplus',
'Accept': '*/*' Accept: '*/*',
} },
}; };
const result = await new Promise((resolve) => { const result = await new Promise((resolve) => {
const req = https.request('https://www.site24x7.com/tools/restapi-tester', options, function (res) { const req = https.request('https://www.site24x7.com/tools/restapi-tester', options, function (res) {
let responseBody = ''; let responseBody = '';
res.on('data', (chunk) => responseBody = responseBody + chunk); res.on('data', (chunk) => (responseBody = responseBody + chunk));
res.on('end', function () { res.on('end', function () {
try { try {
const parsedBody = JSON.parse(responseBody + ''); const parsedBody = JSON.parse(responseBody + '');
@ -1120,8 +1075,10 @@ const internalCertificate = {
// Make sure to write the request body. // Make sure to write the request body.
req.write(formBody); req.write(formBody);
req.end(); req.end();
req.on('error', function (e) { logger.warn(`Failed to test HTTP challenge for domain ${domain}`, e); req.on('error', function (e) {
resolve(undefined); }); logger.warn(`Failed to test HTTP challenge for domain ${domain}`, e);
resolve(undefined);
});
}); });
if (!result) { if (!result) {
@ -1154,7 +1111,7 @@ const internalCertificate = {
const results = {}; const results = {};
for (const domain of domains){ for (const domain of domains) {
results[domain] = await performTestForDomain(domain); results[domain] = await performTestForDomain(domain);
} }
@ -1162,7 +1119,7 @@ const internalCertificate = {
fs.unlinkSync(testChallengeFile); fs.unlinkSync(testChallengeFile);
return results; return results;
} },
}; };
module.exports = internalCertificate; module.exports = internalCertificate;

View file

@ -1,66 +1,63 @@
const _ = require('lodash'); const _ = require('lodash');
const error = require('../lib/error'); const error = require('../lib/error');
const utils = require('../lib/utils'); const utils = require('../lib/utils');
const deadHostModel = require('../models/dead_host'); const deadHostModel = require('../models/dead_host');
const internalHost = require('./host'); const internalHost = require('./host');
const internalNginx = require('./nginx'); const internalNginx = require('./nginx');
const internalAuditLog = require('./audit-log'); const internalAuditLog = require('./audit-log');
const internalCertificate = require('./certificate'); const internalCertificate = require('./certificate');
function 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: (access, data) => { create: (access, data) => {
let 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.can('dead_hosts:create', data) return access
.then((/*access_data*/) => { .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
let domain_name_check_promises = []; const domain_name_check_promises = [];
data.domain_names.map(function (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 Promise.all(domain_name_check_promises) return Promise.all(domain_name_check_promises).then((check_results) => {
.then((check_results) => { check_results.map(function (result) {
check_results.map(function (result) { if (result.is_taken) {
if (result.is_taken) { throw new error.ValidationError(result.hostname + ' is already in use');
throw new error.ValidationError(result.hostname + ' is already in use'); }
}
});
}); });
});
}) })
.then(() => { .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);
data = internalHost.cleanSslHstsData(data); data = internalHost.cleanSslHstsData(data);
return deadHostModel return deadHostModel.query().insertAndFetch(data).then(utils.omitRow(omissions()));
.query()
.insertAndFetch(data)
.then(utils.omitRow(omissions()));
}) })
.then((row) => { .then((row) => {
if (create_certificate) { if (create_certificate) {
return internalCertificate.createQuickCertificate(access, data) return internalCertificate
.createQuickCertificate(access, data)
.then((cert) => { .then((cert) => {
// update host with cert id // update host with cert id
return internalDeadHost.update(access, { return internalDeadHost.update(access, {
id: row.id, id: row.id,
certificate_id: cert.id certificate_id: cert.id,
}); });
}) })
.then(() => { .then(() => {
@ -73,27 +70,27 @@ const internalDeadHost = {
.then((row) => { .then((row) => {
// re-fetch with cert // re-fetch with cert
return internalDeadHost.get(access, { return internalDeadHost.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(deadHostModel, 'dead_host', row) return internalNginx.configure(deadHostModel, 'dead_host', row).then(() => {
.then(() => { return row;
return row; });
});
}) })
.then((row) => { .then((row) => {
data.meta = _.assign({}, data.meta || {}, row.meta); data.meta = _.assign({}, data.meta || {}, row.meta);
// Add to audit log // Add to audit log
return internalAuditLog.add(access, { return internalAuditLog
action: 'created', .add(access, {
object_type: 'dead-host', action: 'created',
object_id: row.id, object_type: 'dead-host',
meta: data object_id: row.id,
}) meta: data,
})
.then(() => { .then(() => {
return row; return row;
}); });
@ -107,34 +104,34 @@ const internalDeadHost = {
* @return {Promise} * @return {Promise}
*/ */
update: (access, data) => { update: (access, data) => {
let 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.can('dead_hosts:update', data.id) return access
.then((/*access_data*/) => { .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
let domain_name_check_promises = []; const domain_name_check_promises = [];
if (typeof data.domain_names !== 'undefined') { if (typeof data.domain_names !== 'undefined') {
data.domain_names.map(function (domain_name) { data.domain_names.map(function (domain_name) {
domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, 'dead', data.id)); domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, 'dead', data.id));
}); });
return Promise.all(domain_name_check_promises) return Promise.all(domain_name_check_promises).then((check_results) => {
.then((check_results) => { check_results.map(function (result) {
check_results.map(function (result) { if (result.is_taken) {
if (result.is_taken) { throw new error.ValidationError(result.hostname + ' is already in use');
throw new error.ValidationError(result.hostname + ' is already in use'); }
}
});
}); });
});
} }
}) })
.then(() => { .then(() => {
return internalDeadHost.get(access, {id: data.id}); return internalDeadHost.get(access, { id: data.id });
}) })
.then((row) => { .then((row) => {
if (row.id !== data.id) { if (row.id !== data.id) {
@ -143,10 +140,11 @@ const internalDeadHost = {
} }
if (create_certificate) { if (create_certificate) {
return internalCertificate.createQuickCertificate(access, { return internalCertificate
domain_names: data.domain_names || row.domain_names, .createQuickCertificate(access, {
meta: _.assign({}, row.meta, data.meta) domain_names: data.domain_names || row.domain_names,
}) meta: _.assign({}, row.meta, data.meta),
})
.then((cert) => { .then((cert) => {
// update host with cert id // update host with cert id
data.certificate_id = cert.id; data.certificate_id = cert.id;
@ -160,42 +158,47 @@ const internalDeadHost = {
}) })
.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.
data = _.assign({}, { data = _.assign(
domain_names: row.domain_names {},
}, data); {
domain_names: row.domain_names,
},
data,
);
data = internalHost.cleanSslHstsData(data, row); data = internalHost.cleanSslHstsData(data, row);
return deadHostModel return deadHostModel
.query() .query()
.where({id: data.id}) .where({ id: data.id })
.patch(data) .patch(data)
.then((saved_row) => { .then((saved_row) => {
// Add to audit log // Add to audit log
return internalAuditLog.add(access, { return internalAuditLog
action: 'updated', .add(access, {
object_type: 'dead-host', action: 'updated',
object_id: row.id, object_type: 'dead-host',
meta: data object_id: row.id,
}) meta: data,
})
.then(() => { .then(() => {
return _.omit(saved_row, omissions()); return _.omit(saved_row, omissions());
}); });
}); });
}) })
.then(() => { .then(() => {
return internalDeadHost.get(access, { return internalDeadHost
id: data.id, .get(access, {
expand: ['owner', 'certificate'] id: data.id,
}) expand: ['owner', 'certificate'],
})
.then((row) => { .then((row) => {
// Configure nginx // Configure nginx
return internalNginx.configure(deadHostModel, 'dead_host', row) return internalNginx.configure(deadHostModel, 'dead_host', row).then((new_meta) => {
.then((new_meta) => { row.meta = new_meta;
row.meta = new_meta; row = internalHost.cleanRowCertificateMeta(row);
row = internalHost.cleanRowCertificateMeta(row); return _.omit(row, omissions());
return _.omit(row, omissions()); });
});
}); });
}); });
}, },
@ -213,14 +216,10 @@ const internalDeadHost = {
data = {}; data = {};
} }
return access.can('dead_hosts:get', data.id) return access
.can('dead_hosts:get', data.id)
.then((access_data) => { .then((access_data) => {
let query = deadHostModel const query = deadHostModel.query().where('is_deleted', 0).andWhere('id', data.id).allowGraph('[owner,certificate]').first();
.query()
.where('is_deleted', 0)
.andWhere('id', data.id)
.allowGraph('[owner,certificate]')
.first();
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));
@ -252,9 +251,10 @@ const internalDeadHost = {
* @returns {Promise} * @returns {Promise}
*/ */
delete: (access, data) => { delete: (access, data) => {
return access.can('dead_hosts:delete', data.id) return access
.can('dead_hosts:delete', data.id)
.then(() => { .then(() => {
return internalDeadHost.get(access, {id: data.id}); return internalDeadHost.get(access, { id: data.id });
}) })
.then((row) => { .then((row) => {
if (!row) { if (!row) {
@ -265,22 +265,21 @@ const internalDeadHost = {
.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('dead_host', row) return internalNginx.deleteConfig('dead_host', row).then(() => {
.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: 'dead-host', object_type: 'dead-host',
object_id: row.id, object_id: row.id,
meta: _.omit(row, omissions()) meta: _.omit(row, omissions()),
}); });
}); });
}) })
@ -297,11 +296,12 @@ const internalDeadHost = {
* @returns {Promise} * @returns {Promise}
*/ */
enable: (access, data) => { enable: (access, data) => {
return access.can('dead_hosts:update', data.id) return access
.can('dead_hosts:update', data.id)
.then(() => { .then(() => {
return internalDeadHost.get(access, { return internalDeadHost.get(access, {
id: data.id, id: data.id,
expand: ['certificate', 'owner'] expand: ['certificate', 'owner'],
}); });
}) })
.then((row) => { .then((row) => {
@ -317,7 +317,7 @@ const internalDeadHost = {
.query() .query()
.where('id', row.id) .where('id', row.id)
.patch({ .patch({
enabled: 1 enabled: 1,
}) })
.then(() => { .then(() => {
// Configure nginx // Configure nginx
@ -326,10 +326,10 @@ const internalDeadHost = {
.then(() => { .then(() => {
// Add to audit log // Add to audit log
return 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()),
}); });
}); });
}) })
@ -346,9 +346,10 @@ const internalDeadHost = {
* @returns {Promise} * @returns {Promise}
*/ */
disable: (access, data) => { disable: (access, data) => {
return access.can('dead_hosts:update', data.id) return access
.can('dead_hosts:update', data.id)
.then(() => { .then(() => {
return internalDeadHost.get(access, {id: data.id}); return internalDeadHost.get(access, { id: data.id });
}) })
.then((row) => { .then((row) => {
if (!row) { if (!row) {
@ -363,22 +364,21 @@ const internalDeadHost = {
.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('dead_host', row) return internalNginx.deleteConfig('dead_host', row).then(() => {
.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: 'dead-host', object_type: 'dead-host',
object_id: row.id, object_id: row.id,
meta: _.omit(row, omissions()) meta: _.omit(row, omissions()),
}); });
}); });
}) })
@ -396,14 +396,10 @@ const internalDeadHost = {
* @returns {Promise} * @returns {Promise}
*/ */
getAll: (access, expand, search_query) => { getAll: (access, expand, search_query) => {
return access.can('dead_hosts:list') return access
.can('dead_hosts:list')
.then((access_data) => { .then((access_data) => {
let query = deadHostModel const query = deadHostModel.query().where('is_deleted', 0).groupBy('id').allowGraph('[owner,certificate]').orderBy('domain_names', 'ASC');
.query()
.where('is_deleted', 0)
.groupBy('id')
.allowGraph('[owner,certificate]')
.orderBy('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));
@ -439,20 +435,16 @@ const internalDeadHost = {
* @returns {Promise} * @returns {Promise}
*/ */
getCount: (user_id, visibility) => { getCount: (user_id, visibility) => {
let query = deadHostModel const query = deadHostModel.query().count('id as count').where('is_deleted', 0);
.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() return query.first().then((row) => {
.then((row) => { return parseInt(row.count, 10);
return parseInt(row.count, 10); });
}); },
}
}; };
module.exports = internalDeadHost; module.exports = internalDeadHost;

View file

@ -1,10 +1,9 @@
const _ = require('lodash'); const _ = require('lodash');
const proxyHostModel = require('../models/proxy_host'); const proxyHostModel = require('../models/proxy_host');
const redirectionHostModel = require('../models/redirection_host'); const redirectionHostModel = require('../models/redirection_host');
const deadHostModel = require('../models/dead_host'); const deadHostModel = require('../models/dead_host');
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.
@ -17,10 +16,10 @@ const internalHost = {
cleanSslHstsData: function (data, existing_data) { cleanSslHstsData: function (data, existing_data) {
existing_data = existing_data === undefined ? {} : existing_data; existing_data = existing_data === undefined ? {} : existing_data;
let combined_data = _.assign({}, existing_data, data); const combined_data = _.assign({}, existing_data, data);
if (!combined_data.certificate_id) { if (!combined_data.certificate_id) {
combined_data.ssl_forced = false; combined_data.ssl_forced = false;
combined_data.hsts_subdomains = false; combined_data.hsts_subdomains = false;
} }
@ -69,47 +68,36 @@ const internalHost = {
* @returns {Promise} * @returns {Promise}
*/ */
getHostsWithDomains: function (domain_names) { getHostsWithDomains: function (domain_names) {
let promises = [ const promises = [proxyHostModel.query().where('is_deleted', 0), redirectionHostModel.query().where('is_deleted', 0), deadHostModel.query().where('is_deleted', 0)];
proxyHostModel
.query()
.where('is_deleted', 0),
redirectionHostModel
.query()
.where('is_deleted', 0),
deadHostModel
.query()
.where('is_deleted', 0)
];
return Promise.all(promises) return Promise.all(promises).then((promises_results) => {
.then((promises_results) => { const response_object = {
let response_object = { total_count: 0,
total_count: 0, dead_hosts: [],
dead_hosts: [], proxy_hosts: [],
proxy_hosts: [], redirection_hosts: [],
redirection_hosts: [] };
};
if (promises_results[0]) { if (promises_results[0]) {
// Proxy Hosts // Proxy Hosts
response_object.proxy_hosts = internalHost._getHostsWithDomains(promises_results[0], domain_names); response_object.proxy_hosts = internalHost._getHostsWithDomains(promises_results[0], domain_names);
response_object.total_count += response_object.proxy_hosts.length; response_object.total_count += response_object.proxy_hosts.length;
} }
if (promises_results[1]) { if (promises_results[1]) {
// Redirection Hosts // Redirection Hosts
response_object.redirection_hosts = internalHost._getHostsWithDomains(promises_results[1], domain_names); response_object.redirection_hosts = internalHost._getHostsWithDomains(promises_results[1], domain_names);
response_object.total_count += response_object.redirection_hosts.length; response_object.total_count += response_object.redirection_hosts.length;
} }
if (promises_results[2]) { if (promises_results[2]) {
// Dead Hosts // Dead Hosts
response_object.dead_hosts = internalHost._getHostsWithDomains(promises_results[2], domain_names); response_object.dead_hosts = internalHost._getHostsWithDomains(promises_results[2], domain_names);
response_object.total_count += response_object.dead_hosts.length; response_object.total_count += response_object.dead_hosts.length;
} }
return response_object; return response_object;
}); });
}, },
/** /**
@ -121,7 +109,7 @@ const internalHost = {
* @returns {Promise} * @returns {Promise}
*/ */
isHostnameTaken: function (hostname, ignore_type, ignore_id) { isHostnameTaken: function (hostname, ignore_type, ignore_id) {
let promises = [ const promises = [
proxyHostModel proxyHostModel
.query() .query()
.where('is_deleted', 0) .where('is_deleted', 0)
@ -133,39 +121,38 @@ const internalHost = {
deadHostModel deadHostModel
.query() .query()
.where('is_deleted', 0) .where('is_deleted', 0)
.andWhere('domain_names', 'like', '%' + hostname + '%') .andWhere('domain_names', 'like', '%' + hostname + '%'),
]; ];
return Promise.all(promises) return Promise.all(promises).then((promises_results) => {
.then((promises_results) => { let is_taken = false;
let is_taken = false;
if (promises_results[0]) { if (promises_results[0]) {
// Proxy Hosts // Proxy Hosts
if (internalHost._checkHostnameRecordsTaken(hostname, promises_results[0], ignore_type === 'proxy' && ignore_id ? ignore_id : 0)) { if (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 (internalHost._checkHostnameRecordsTaken(hostname, promises_results[1], ignore_type === 'redirection' && ignore_id ? ignore_id : 0)) { if (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 (internalHost._checkHostnameRecordsTaken(hostname, promises_results[2], ignore_type === 'dead' && ignore_id ? ignore_id : 0)) { if (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,
is_taken: is_taken is_taken,
}; };
}); });
}, },
/** /**
@ -203,7 +190,7 @@ const internalHost = {
* @returns {Array} * @returns {Array}
*/ */
_getHostsWithDomains: function (hosts, domain_names) { _getHostsWithDomains: function (hosts, domain_names) {
let response = []; const response = [];
if (hosts && hosts.length) { if (hosts && hosts.length) {
hosts.map(function (host) { hosts.map(function (host) {
@ -224,8 +211,7 @@ const internalHost = {
} }
return response; return response;
} },
}; };
module.exports = internalHost; module.exports = internalHost;

View file

@ -1,11 +1,11 @@
const https = require('https'); const https = require('https');
const fs = require('fs'); const fs = require('fs');
const logger = require('../logger').ip_ranges; const logger = require('../logger').ip_ranges;
const error = require('../lib/error'); const error = require('../lib/error');
const utils = require('../lib/utils'); const utils = require('../lib/utils');
const internalNginx = require('./nginx'); const internalNginx = require('./nginx');
const CLOUDFRONT_URL = 'https://ip-ranges.amazonaws.com/ip-ranges.json'; const CLOUDFRONT_URL = 'https://ip-ranges.amazonaws.com/ip-ranges.json';
const CLOUDFARE_V4_URL = 'https://www.cloudflare.com/ips-v4'; const CLOUDFARE_V4_URL = 'https://www.cloudflare.com/ips-v4';
const CLOUDFARE_V6_URL = 'https://www.cloudflare.com/ips-v6'; const CLOUDFARE_V6_URL = 'https://www.cloudflare.com/ips-v6';
@ -13,11 +13,10 @@ 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 * Number(process.env.IPRT),
interval_timeout: 1000 * 60 * 60 * Number(process.env.IPRT), interval: null,
interval: null,
interval_processing: false, interval_processing: false,
iteration_count: 0, iteration_count: 0,
initTimer: () => { initTimer: () => {
if (process.env.SKIP_IP_RANGES === 'false') { if (process.env.SKIP_IP_RANGES === 'false') {
@ -29,19 +28,21 @@ const internalIpRanges = {
fetchUrl: (url) => { fetchUrl: (url) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
logger.info('Fetching ' + url); logger.info('Fetching ' + url);
return https.get(url, (res) => { return https
res.setEncoding('utf8'); .get(url, (res) => {
let raw_data = ''; res.setEncoding('utf8');
res.on('data', (chunk) => { let raw_data = '';
raw_data += chunk; res.on('data', (chunk) => {
}); raw_data += chunk;
});
res.on('end', () => { res.on('end', () => {
resolve(raw_data); resolve(raw_data);
});
})
.on('error', (err) => {
reject(err);
}); });
}).on('error', (err) => {
reject(err);
});
}); });
}, },
@ -55,9 +56,10 @@ const internalIpRanges = {
let ip_ranges = []; let ip_ranges = [];
return internalIpRanges.fetchUrl(CLOUDFRONT_URL) return internalIpRanges
.fetchUrl(CLOUDFRONT_URL)
.then((cloudfront_data) => { .then((cloudfront_data) => {
let data = JSON.parse(cloudfront_data); const data = JSON.parse(cloudfront_data);
if (data && typeof data.prefixes !== 'undefined') { if (data && typeof data.prefixes !== 'undefined') {
data.prefixes.map((item) => { data.prefixes.map((item) => {
@ -79,31 +81,30 @@ const internalIpRanges = {
return internalIpRanges.fetchUrl(CLOUDFARE_V4_URL); return internalIpRanges.fetchUrl(CLOUDFARE_V4_URL);
}) })
.then((cloudfare_data) => { .then((cloudfare_data) => {
let items = cloudfare_data.split('\n').filter((line) => regIpV4.test(line)); const 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) => {
let items = cloudfare_data.split('\n').filter((line) => regIpV6.test(line)); const items = cloudfare_data.split('\n').filter((line) => regIpV6.test(line));
ip_ranges = [... ip_ranges, ... items]; ip_ranges = [...ip_ranges, ...items];
}) })
.then(() => { .then(() => {
let clean_ip_ranges = []; const 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 internalIpRanges.generateConfig(clean_ip_ranges) return internalIpRanges.generateConfig(clean_ip_ranges).then(() => {
.then(() => { if (internalIpRanges.iteration_count) {
if (internalIpRanges.iteration_count) { // Reload nginx
// Reload nginx return internalNginx.reload();
return internalNginx.reload(); }
} });
});
}) })
.then(() => { .then(() => {
internalIpRanges.interval_processing = false; internalIpRanges.interval_processing = false;
@ -124,18 +125,18 @@ 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;
let filename = '/data/nginx/ip_ranges.conf'; const filename = '/data/nginx/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 error.ConfigurationError(err.message)); reject(new error.ConfigurationError(err.message));
return; return;
} }
renderEngine renderEngine
.parseAndRender(template, {ip_ranges: ip_ranges}) .parseAndRender(template, { 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) => {
@ -143,7 +144,7 @@ const internalIpRanges = {
reject(new error.ConfigurationError(err.message)); reject(new error.ConfigurationError(err.message));
}); });
}); });
} },
}; };
module.exports = internalIpRanges; module.exports = internalIpRanges;

View file

@ -1,14 +1,13 @@
const _ = require('lodash'); const _ = require('lodash');
const fs = require('fs'); const fs = require('fs');
const logger = require('../logger').nginx; const logger = require('../logger').nginx;
const config = require('../lib/config'); const config = require('../lib/config');
const utils = require('../lib/utils'); const utils = require('../lib/utils');
const error = require('../lib/error'); const error = require('../lib/error');
const NgxPidFilePath = '/usr/local/nginx/logs/nginx.pid'; const NgxPidFilePath = '/usr/local/nginx/logs/nginx.pid';
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
@ -26,7 +25,8 @@ const internalNginx = {
configure: (model, host_type, host) => { configure: (model, host_type, host) => {
let combined_meta = {}; let combined_meta = {};
return internalNginx.test() return internalNginx
.test()
.then(() => { .then(() => {
// Nginx is OK // Nginx is OK
// We're deleting this config regardless. // We're deleting this config regardless.
@ -39,28 +39,26 @@ const internalNginx = {
}) })
.then(() => { .then(() => {
// Test nginx again and update meta with result // Test nginx again and update meta with result
return internalNginx.test() return internalNginx
.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 return model.query().where('id', host.id).patch({
.query() meta: combined_meta,
.where('id', host.id) });
.patch({
meta: combined_meta
});
}) })
.catch((err) => { .catch((err) => {
// Remove the error_log line because it's a docker-ism false positive that doesn't need to be reported. // Remove the error_log line because it's a docker-ism false positive that doesn't need to be reported.
// It will always look like this: // It will always look like this:
// nginx: [alert] could not open error log file: open() "/dev/null" failed (6: No such device or address) // nginx: [alert] could not open error log file: open() "/dev/null" failed (6: No such device or address)
let valid_lines = []; const valid_lines = [];
let err_lines = err.message.split('\n'); const err_lines = err.message.split('\n');
err_lines.map(function (line) { err_lines.map(function (line) {
if (line.indexOf('/dev/null') === -1) { if (line.indexOf('/dev/null') === -1) {
valid_lines.push(line); valid_lines.push(line);
@ -74,14 +72,14 @@ const internalNginx = {
// 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);
@ -115,22 +113,21 @@ const internalNginx = {
*/ */
reload: () => { reload: () => {
return internalNginx.test() return internalNginx.test().then(() => {
.then(() => { if (fs.existsSync(NgxPidFilePath)) {
if (fs.existsSync(NgxPidFilePath)) { const ngxPID = fs.readFileSync(NgxPidFilePath, 'utf8').trim();
const ngxPID = fs.readFileSync(NgxPidFilePath, 'utf8').trim(); if (ngxPID.length > 0) {
if (ngxPID.length > 0) { logger.info('Reloading Nginx');
logger.info('Reloading Nginx'); utils.exec('nginx -s reload');
utils.exec('nginx -s reload');
} else {
logger.info('Starting Nginx');
utils.execfg('nginx -e stderr');
}
} else { } else {
logger.info('Starting Nginx'); logger.info('Starting Nginx');
utils.execfg('nginx -e stderr'); utils.execfg('nginx -e stderr');
} }
}); } else {
logger.info('Starting Nginx');
utils.execfg('nginx -e stderr');
}
});
}, },
/** /**
@ -155,22 +152,18 @@ 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 error.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++) {
let locationCopy = Object.assign({}, {access_list_id: host.access_list_id}, {certificate_id: host.certificate_id}, const locationCopy = Object.assign({}, { access_list_id: host.access_list_id }, { certificate_id: host.certificate_id }, { ssl_forced: host.ssl_forced }, { caching_enabled: host.caching_enabled }, { block_exploits: host.block_exploits }, { allow_websocket_upgrade: host.allow_websocket_upgrade }, { http2_support: host.http2_support }, { hsts_enabled: host.hsts_enabled }, { hsts_subdomains: host.hsts_subdomains }, { access_list: host.access_list }, { certificate: host.certificate }, host.locations[i]);
{ssl_forced: host.ssl_forced}, {caching_enabled: host.caching_enabled}, {block_exploits: host.block_exploits},
{allow_websocket_upgrade: host.allow_websocket_upgrade}, {http2_support: host.http2_support},
{hsts_enabled: host.hsts_enabled}, {hsts_subdomains: host.hsts_subdomains}, {access_list: host.access_list},
{certificate: host.certificate}, host.locations[i]);
if (locationCopy.forward_host.indexOf('/') > -1) { if (locationCopy.forward_host.indexOf('/') > -1) {
const split = locationCopy.forward_host.split('/'); const split = locationCopy.forward_host.split('/');
@ -179,14 +172,11 @@ const internalNginx = {
locationCopy.forward_path = `/${split.join('/')}`; locationCopy.forward_path = `/${split.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));
}); });
}, },
@ -206,10 +196,10 @@ const internalNginx = {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let template = null; let template = null;
let filename = internalNginx.getConfigName(nice_host_type, host.id); const 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 error.ConfigurationError(err.message)); reject(new error.ConfigurationError(err.message));
return; return;
@ -227,8 +217,8 @@ const internalNginx = {
} }
if (host.locations) { if (host.locations) {
//logger.info ('host.locations = ' + JSON.stringify(host.locations, null, 2)); // logger.info ('host.locations = ' + JSON.stringify(host.locations, null, 2));
origLocations = [].concat(host.locations); origLocations = [].concat(host.locations);
locationsPromise = internalNginx.renderLocations(host).then((renderedLocations) => { locationsPromise = internalNginx.renderLocations(host).then((renderedLocations) => {
host.locations = renderedLocations; host.locations = renderedLocations;
}); });
@ -239,7 +229,6 @@ const internalNginx = {
host.use_default_location = false; host.use_default_location = false;
} }
}); });
} else { } else {
locationsPromise = Promise.resolve(); locationsPromise = Promise.resolve();
} }
@ -251,7 +240,7 @@ 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' });
if (config.debug()) { if (config.debug()) {
logger.success('Wrote config:', filename, config_text); logger.success('Wrote config:', filename, config_text);
@ -296,7 +285,6 @@ const internalNginx = {
return host_type.replace(new RegExp('-', 'g'), '_'); return host_type.replace(new RegExp('-', 'g'), '_');
}, },
/** /**
* @param {String} host_type * @param {String} host_type
* @param {Object} [host] * @param {Object} [host]
@ -304,10 +292,10 @@ 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(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id); const config_file = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id);
const config_file_err = config_file + '.err'; 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);
if (delete_err_file) { if (delete_err_file) {
internalNginx.deleteFile(config_file_err); internalNginx.deleteFile(config_file_err);
@ -322,10 +310,10 @@ const internalNginx = {
* @returns {Promise} * @returns {Promise}
*/ */
renameConfigAsError: (host_type, host) => { renameConfigAsError: (host_type, host) => {
const config_file = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id); const config_file = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id);
const config_file_err = config_file + '.err'; 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, () => {
// ignore result, continue // ignore result, continue
fs.rename(config_file, config_file_err, () => { fs.rename(config_file, config_file_err, () => {
@ -342,7 +330,7 @@ const internalNginx = {
* @returns {Promise} * @returns {Promise}
*/ */
bulkGenerateConfigs: (host_type, hosts) => { bulkGenerateConfigs: (host_type, hosts) => {
let promises = []; const promises = [];
hosts.map(function (host) { hosts.map(function (host) {
promises.push(internalNginx.generateConfig(host_type, host)); promises.push(internalNginx.generateConfig(host_type, host));
}); });
@ -356,7 +344,7 @@ const internalNginx = {
* @returns {Promise} * @returns {Promise}
*/ */
bulkDeleteConfigs: (host_type, hosts) => { bulkDeleteConfigs: (host_type, hosts) => {
let promises = []; const promises = [];
hosts.map(function (host) { hosts.map(function (host) {
promises.push(internalNginx.deleteConfig(host_type, host, true)); promises.push(internalNginx.deleteConfig(host_type, host, true));
}); });
@ -382,7 +370,7 @@ const internalNginx = {
} }
return true; return true;
} },
}; };
module.exports = internalNginx; module.exports = internalNginx;

View file

@ -1,66 +1,63 @@
const _ = require('lodash'); const _ = require('lodash');
const error = require('../lib/error'); const error = require('../lib/error');
const utils = require('../lib/utils'); const utils = require('../lib/utils');
const proxyHostModel = require('../models/proxy_host'); const proxyHostModel = require('../models/proxy_host');
const internalHost = require('./host'); const internalHost = require('./host');
const internalNginx = require('./nginx'); const internalNginx = require('./nginx');
const internalAuditLog = require('./audit-log'); const internalAuditLog = require('./audit-log');
const internalCertificate = require('./certificate'); const internalCertificate = require('./certificate');
function omissions () { function omissions() {
return ['is_deleted']; return ['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 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.can('proxy_hosts:create', data) return access
.can('proxy_hosts:create', data)
.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
let domain_name_check_promises = []; const domain_name_check_promises = [];
data.domain_names.map(function (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 Promise.all(domain_name_check_promises) return Promise.all(domain_name_check_promises).then((check_results) => {
.then((check_results) => { check_results.map(function (result) {
check_results.map(function (result) { if (result.is_taken) {
if (result.is_taken) { throw new error.ValidationError(result.hostname + ' is already in use');
throw new error.ValidationError(result.hostname + ' is already in use'); }
}
});
}); });
});
}) })
.then(() => { .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);
data = internalHost.cleanSslHstsData(data); data = internalHost.cleanSslHstsData(data);
return proxyHostModel return proxyHostModel.query().insertAndFetch(data).then(utils.omitRow(omissions()));
.query()
.insertAndFetch(data)
.then(utils.omitRow(omissions()));
}) })
.then((row) => { .then((row) => {
if (create_certificate) { if (create_certificate) {
return internalCertificate.createQuickCertificate(access, data) return internalCertificate
.createQuickCertificate(access, data)
.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(() => {
@ -73,28 +70,28 @@ const internalProxyHost = {
.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) return internalNginx.configure(proxyHostModel, 'proxy_host', row).then(() => {
.then(() => { return row;
return row; });
});
}) })
.then((row) => { .then((row) => {
// Audit log // Audit log
data.meta = _.assign({}, data.meta || {}, row.meta); data.meta = _.assign({}, data.meta || {}, row.meta);
// Add to audit log // Add to audit log
return internalAuditLog.add(access, { return internalAuditLog
action: 'created', .add(access, {
object_type: 'proxy-host', action: 'created',
object_id: row.id, object_type: 'proxy-host',
meta: data object_id: row.id,
}) meta: data,
})
.then(() => { .then(() => {
return row; return row;
}); });
@ -108,34 +105,34 @@ const internalProxyHost = {
* @return {Promise} * @return {Promise}
*/ */
update: (access, data) => { update: (access, data) => {
let 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.can('proxy_hosts:update', data.id) return access
.then((/*access_data*/) => { .can('proxy_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
let domain_name_check_promises = []; const domain_name_check_promises = [];
if (typeof data.domain_names !== 'undefined') { if (typeof data.domain_names !== 'undefined') {
data.domain_names.map(function (domain_name) { data.domain_names.map(function (domain_name) {
domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, 'proxy', data.id)); domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, 'proxy', data.id));
}); });
return Promise.all(domain_name_check_promises) return Promise.all(domain_name_check_promises).then((check_results) => {
.then((check_results) => { check_results.map(function (result) {
check_results.map(function (result) { if (result.is_taken) {
if (result.is_taken) { throw new error.ValidationError(result.hostname + ' is already in use');
throw new error.ValidationError(result.hostname + ' is already in use'); }
}
});
}); });
});
} }
}) })
.then(() => { .then(() => {
return internalProxyHost.get(access, {id: data.id}); return internalProxyHost.get(access, { id: data.id });
}) })
.then((row) => { .then((row) => {
if (row.id !== data.id) { if (row.id !== data.id) {
@ -144,10 +141,11 @@ const internalProxyHost = {
} }
if (create_certificate) { if (create_certificate) {
return internalCertificate.createQuickCertificate(access, { return internalCertificate
domain_names: data.domain_names || row.domain_names, .createQuickCertificate(access, {
meta: _.assign({}, row.meta, data.meta) domain_names: data.domain_names || row.domain_names,
}) meta: _.assign({}, row.meta, data.meta),
})
.then((cert) => { .then((cert) => {
// update host with cert id // update host with cert id
data.certificate_id = cert.id; data.certificate_id = cert.id;
@ -161,47 +159,52 @@ const internalProxyHost = {
}) })
.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.
data = _.assign({}, { data = _.assign(
domain_names: row.domain_names {},
}, data); {
domain_names: row.domain_names,
},
data,
);
data = internalHost.cleanSslHstsData(data, row); data = internalHost.cleanSslHstsData(data, row);
return proxyHostModel return proxyHostModel
.query() .query()
.where({id: data.id}) .where({ id: data.id })
.patch(data) .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.add(access, { return internalAuditLog
action: 'updated', .add(access, {
object_type: 'proxy-host', action: 'updated',
object_id: row.id, object_type: 'proxy-host',
meta: data object_id: row.id,
}) meta: data,
})
.then(() => { .then(() => {
return saved_row; return saved_row;
}); });
}); });
}) })
.then(() => { .then(() => {
return internalProxyHost.get(access, { return internalProxyHost
id: data.id, .get(access, {
expand: ['owner', 'certificate', 'access_list.[clients,items]'] id: data.id,
}) expand: ['owner', 'certificate', 'access_list.[clients,items]'],
})
.then((row) => { .then((row) => {
if (!row.enabled) { if (!row.enabled) {
// No need to add nginx config if host is disabled // No need to add nginx config if host is disabled
return row; return row;
} }
// Configure nginx // Configure nginx
return internalNginx.configure(proxyHostModel, 'proxy_host', row) return internalNginx.configure(proxyHostModel, 'proxy_host', row).then((new_meta) => {
.then((new_meta) => { row.meta = new_meta;
row.meta = new_meta; row = internalHost.cleanRowCertificateMeta(row);
row = internalHost.cleanRowCertificateMeta(row); return _.omit(row, omissions());
return _.omit(row, omissions()); });
});
}); });
}); });
}, },
@ -219,14 +222,10 @@ const internalProxyHost = {
data = {}; data = {};
} }
return access.can('proxy_hosts:get', data.id) return access
.can('proxy_hosts:get', data.id)
.then((access_data) => { .then((access_data) => {
let query = proxyHostModel const query = proxyHostModel.query().where('is_deleted', 0).andWhere('id', data.id).allowGraph('[owner,access_list.[clients,items],certificate]').first();
.query()
.where('is_deleted', 0)
.andWhere('id', data.id)
.allowGraph('[owner,access_list.[clients,items],certificate]')
.first();
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));
@ -259,9 +258,10 @@ const internalProxyHost = {
* @returns {Promise} * @returns {Promise}
*/ */
delete: (access, data) => { delete: (access, data) => {
return access.can('proxy_hosts:delete', data.id) return access
.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) { if (!row) {
@ -272,22 +272,21 @@ const internalProxyHost = {
.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) return internalNginx.deleteConfig('proxy_host', row).then(() => {
.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()),
}); });
}); });
}) })
@ -304,11 +303,12 @@ const internalProxyHost = {
* @returns {Promise} * @returns {Promise}
*/ */
enable: (access, data) => { enable: (access, data) => {
return access.can('proxy_hosts:update', data.id) return access
.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) => {
@ -324,7 +324,7 @@ const internalProxyHost = {
.query() .query()
.where('id', row.id) .where('id', row.id)
.patch({ .patch({
enabled: 1 enabled: 1,
}) })
.then(() => { .then(() => {
// Configure nginx // Configure nginx
@ -333,10 +333,10 @@ const internalProxyHost = {
.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()),
}); });
}); });
}) })
@ -353,9 +353,10 @@ const internalProxyHost = {
* @returns {Promise} * @returns {Promise}
*/ */
disable: (access, data) => { disable: (access, data) => {
return access.can('proxy_hosts:update', data.id) return access
.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) { if (!row) {
@ -370,22 +371,21 @@ const internalProxyHost = {
.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) return internalNginx.deleteConfig('proxy_host', row).then(() => {
.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()),
}); });
}); });
}) })
@ -403,14 +403,10 @@ const internalProxyHost = {
* @returns {Promise} * @returns {Promise}
*/ */
getAll: (access, expand, search_query) => { getAll: (access, expand, search_query) => {
return access.can('proxy_hosts:list') return access
.can('proxy_hosts:list')
.then((access_data) => { .then((access_data) => {
let query = proxyHostModel const query = proxyHostModel.query().where('is_deleted', 0).groupBy('id').allowGraph('[owner,access_list,certificate]').orderBy('domain_names', 'ASC');
.query()
.where('is_deleted', 0)
.groupBy('id')
.allowGraph('[owner,access_list,certificate]')
.orderBy('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));
@ -446,20 +442,16 @@ const internalProxyHost = {
* @returns {Promise} * @returns {Promise}
*/ */
getCount: (user_id, visibility) => { getCount: (user_id, visibility) => {
let query = proxyHostModel const query = proxyHostModel.query().count('id as count').where('is_deleted', 0);
.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() return query.first().then((row) => {
.then((row) => { return parseInt(row.count, 10);
return parseInt(row.count, 10); });
}); },
}
}; };
module.exports = internalProxyHost; module.exports = internalProxyHost;

View file

@ -1,66 +1,63 @@
const _ = require('lodash'); const _ = require('lodash');
const error = require('../lib/error'); const error = require('../lib/error');
const utils = require('../lib/utils'); const utils = require('../lib/utils');
const redirectionHostModel = require('../models/redirection_host'); const redirectionHostModel = require('../models/redirection_host');
const internalHost = require('./host'); const internalHost = require('./host');
const internalNginx = require('./nginx'); const internalNginx = require('./nginx');
const internalAuditLog = require('./audit-log'); const internalAuditLog = require('./audit-log');
const internalCertificate = require('./certificate'); const internalCertificate = require('./certificate');
function 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 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.can('redirection_hosts:create', data) return access
.then((/*access_data*/) => { .can('redirection_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
let domain_name_check_promises = []; const domain_name_check_promises = [];
data.domain_names.map(function (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 Promise.all(domain_name_check_promises) return Promise.all(domain_name_check_promises).then((check_results) => {
.then((check_results) => { check_results.map(function (result) {
check_results.map(function (result) { if (result.is_taken) {
if (result.is_taken) { throw new error.ValidationError(result.hostname + ' is already in use');
throw new error.ValidationError(result.hostname + ' is already in use'); }
}
});
}); });
});
}) })
.then(() => { .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);
data = internalHost.cleanSslHstsData(data); data = internalHost.cleanSslHstsData(data);
return redirectionHostModel return redirectionHostModel.query().insertAndFetch(data).then(utils.omitRow(omissions()));
.query()
.insertAndFetch(data)
.then(utils.omitRow(omissions()));
}) })
.then((row) => { .then((row) => {
if (create_certificate) { if (create_certificate) {
return internalCertificate.createQuickCertificate(access, data) return internalCertificate
.createQuickCertificate(access, data)
.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(() => {
@ -72,27 +69,27 @@ const internalRedirectionHost = {
.then((row) => { .then((row) => {
// 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) return internalNginx.configure(redirectionHostModel, 'redirection_host', row).then(() => {
.then(() => { return row;
return row; });
});
}) })
.then((row) => { .then((row) => {
data.meta = _.assign({}, data.meta || {}, row.meta); data.meta = _.assign({}, data.meta || {}, row.meta);
// Add to audit log // Add to audit log
return internalAuditLog.add(access, { return internalAuditLog
action: 'created', .add(access, {
object_type: 'redirection-host', action: 'created',
object_id: row.id, object_type: 'redirection-host',
meta: data object_id: row.id,
}) meta: data,
})
.then(() => { .then(() => {
return row; return row;
}); });
@ -106,34 +103,34 @@ const internalRedirectionHost = {
* @return {Promise} * @return {Promise}
*/ */
update: (access, data) => { update: (access, data) => {
let 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.can('redirection_hosts:update', data.id) return access
.then((/*access_data*/) => { .can('redirection_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
let domain_name_check_promises = []; const domain_name_check_promises = [];
if (typeof data.domain_names !== 'undefined') { if (typeof data.domain_names !== 'undefined') {
data.domain_names.map(function (domain_name) { data.domain_names.map(function (domain_name) {
domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, 'redirection', data.id)); domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, 'redirection', data.id));
}); });
return Promise.all(domain_name_check_promises) return Promise.all(domain_name_check_promises).then((check_results) => {
.then((check_results) => { check_results.map(function (result) {
check_results.map(function (result) { if (result.is_taken) {
if (result.is_taken) { throw new error.ValidationError(result.hostname + ' is already in use');
throw new error.ValidationError(result.hostname + ' is already in use'); }
}
});
}); });
});
} }
}) })
.then(() => { .then(() => {
return internalRedirectionHost.get(access, {id: data.id}); return internalRedirectionHost.get(access, { id: data.id });
}) })
.then((row) => { .then((row) => {
if (row.id !== data.id) { if (row.id !== data.id) {
@ -142,10 +139,11 @@ const internalRedirectionHost = {
} }
if (create_certificate) { if (create_certificate) {
return internalCertificate.createQuickCertificate(access, { return internalCertificate
domain_names: data.domain_names || row.domain_names, .createQuickCertificate(access, {
meta: _.assign({}, row.meta, data.meta) domain_names: data.domain_names || row.domain_names,
}) meta: _.assign({}, row.meta, data.meta),
})
.then((cert) => { .then((cert) => {
// update host with cert id // update host with cert id
data.certificate_id = cert.id; data.certificate_id = cert.id;
@ -159,42 +157,47 @@ const internalRedirectionHost = {
}) })
.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.
data = _.assign({}, { data = _.assign(
domain_names: row.domain_names {},
}, data); {
domain_names: row.domain_names,
},
data,
);
data = internalHost.cleanSslHstsData(data, row); data = internalHost.cleanSslHstsData(data, row);
return redirectionHostModel return redirectionHostModel
.query() .query()
.where({id: data.id}) .where({ id: data.id })
.patch(data) .patch(data)
.then((saved_row) => { .then((saved_row) => {
// Add to audit log // Add to audit log
return internalAuditLog.add(access, { return internalAuditLog
action: 'updated', .add(access, {
object_type: 'redirection-host', action: 'updated',
object_id: row.id, object_type: 'redirection-host',
meta: data object_id: row.id,
}) meta: data,
})
.then(() => { .then(() => {
return _.omit(saved_row, omissions()); return _.omit(saved_row, omissions());
}); });
}); });
}) })
.then(() => { .then(() => {
return internalRedirectionHost.get(access, { return internalRedirectionHost
id: data.id, .get(access, {
expand: ['owner', 'certificate'] id: data.id,
}) expand: ['owner', 'certificate'],
})
.then((row) => { .then((row) => {
// Configure nginx // Configure nginx
return internalNginx.configure(redirectionHostModel, 'redirection_host', row) return internalNginx.configure(redirectionHostModel, 'redirection_host', row).then((new_meta) => {
.then((new_meta) => { row.meta = new_meta;
row.meta = new_meta; row = internalHost.cleanRowCertificateMeta(row);
row = internalHost.cleanRowCertificateMeta(row); return _.omit(row, omissions());
return _.omit(row, omissions()); });
});
}); });
}); });
}, },
@ -212,14 +215,10 @@ const internalRedirectionHost = {
data = {}; data = {};
} }
return access.can('redirection_hosts:get', data.id) return access
.can('redirection_hosts:get', data.id)
.then((access_data) => { .then((access_data) => {
let query = redirectionHostModel const query = redirectionHostModel.query().where('is_deleted', 0).andWhere('id', data.id).allowGraph('[owner,certificate]').first();
.query()
.where('is_deleted', 0)
.andWhere('id', data.id)
.allowGraph('[owner,certificate]')
.first();
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));
@ -252,9 +251,10 @@ const internalRedirectionHost = {
* @returns {Promise} * @returns {Promise}
*/ */
delete: (access, data) => { delete: (access, data) => {
return access.can('redirection_hosts:delete', data.id) return access
.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) { if (!row) {
@ -265,22 +265,21 @@ const internalRedirectionHost = {
.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) return internalNginx.deleteConfig('redirection_host', row).then(() => {
.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()),
}); });
}); });
}) })
@ -297,11 +296,12 @@ const internalRedirectionHost = {
* @returns {Promise} * @returns {Promise}
*/ */
enable: (access, data) => { enable: (access, data) => {
return access.can('redirection_hosts:update', data.id) return access
.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) => {
@ -317,7 +317,7 @@ const internalRedirectionHost = {
.query() .query()
.where('id', row.id) .where('id', row.id)
.patch({ .patch({
enabled: 1 enabled: 1,
}) })
.then(() => { .then(() => {
// Configure nginx // Configure nginx
@ -326,10 +326,10 @@ const internalRedirectionHost = {
.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()),
}); });
}); });
}) })
@ -346,9 +346,10 @@ const internalRedirectionHost = {
* @returns {Promise} * @returns {Promise}
*/ */
disable: (access, data) => { disable: (access, data) => {
return access.can('redirection_hosts:update', data.id) return access
.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) { if (!row) {
@ -363,22 +364,21 @@ const internalRedirectionHost = {
.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) return internalNginx.deleteConfig('redirection_host', row).then(() => {
.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()),
}); });
}); });
}) })
@ -396,14 +396,10 @@ const internalRedirectionHost = {
* @returns {Promise} * @returns {Promise}
*/ */
getAll: (access, expand, search_query) => { getAll: (access, expand, search_query) => {
return access.can('redirection_hosts:list') return access
.can('redirection_hosts:list')
.then((access_data) => { .then((access_data) => {
let query = redirectionHostModel const query = redirectionHostModel.query().where('is_deleted', 0).groupBy('id').allowGraph('[owner,certificate]').orderBy('domain_names', 'ASC');
.query()
.where('is_deleted', 0)
.groupBy('id')
.allowGraph('[owner,certificate]')
.orderBy('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));
@ -439,20 +435,16 @@ const internalRedirectionHost = {
* @returns {Promise} * @returns {Promise}
*/ */
getCount: (user_id, visibility) => { getCount: (user_id, visibility) => {
let query = redirectionHostModel const query = redirectionHostModel.query().count('id as count').where('is_deleted', 0);
.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() return query.first().then((row) => {
.then((row) => { return parseInt(row.count, 10);
return parseInt(row.count, 10); });
}); },
}
}; };
module.exports = internalRedirectionHost; module.exports = internalRedirectionHost;

View file

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

View file

@ -1,10 +1,9 @@
const fs = require('fs'); const fs = require('fs');
const error = require('../lib/error'); const error = require('../lib/error');
const settingModel = require('../models/setting'); const settingModel = require('../models/setting');
const internalNginx = require('./nginx'); const internalNginx = require('./nginx');
const internalSetting = { const internalSetting = {
/** /**
* @param {Access} access * @param {Access} access
* @param {Object} data * @param {Object} data
@ -12,9 +11,10 @@ const internalSetting = {
* @return {Promise} * @return {Promise}
*/ */
update: (access, data) => { update: (access, data) => {
return access.can('settings:update', data.id) return access
.then((/*access_data*/) => { .can('settings:update', data.id)
return internalSetting.get(access, {id: data.id}); .then((/* access_data */) => {
return internalSetting.get(access, { id: data.id });
}) })
.then((row) => { .then((row) => {
if (row.id !== data.id) { if (row.id !== data.id) {
@ -22,25 +22,23 @@ const internalSetting = {
throw new error.InternalValidationError('Setting could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); throw new error.InternalValidationError('Setting could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
} }
return settingModel return settingModel.query().where({ id: data.id }).patch(data);
.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/etc/index.html', row.meta.html, {encoding: 'utf8'}); fs.writeFileSync('/data/nginx/etc/index.html', row.meta.html, { encoding: 'utf8' });
} }
// Configure nginx // Configure nginx
return internalNginx.deleteConfig('default') return internalNginx
.deleteConfig('default')
.then(() => { .then(() => {
return internalNginx.generateConfig('default', row); return internalNginx.generateConfig('default', row);
}) })
@ -53,8 +51,9 @@ const internalSetting = {
.then(() => { .then(() => {
return row; return row;
}) })
.catch((/*err*/) => { .catch((/* err */) => {
internalNginx.deleteConfig('default') internalNginx
.deleteConfig('default')
.then(() => { .then(() => {
return internalNginx.test(); return internalNginx.test();
}) })
@ -79,12 +78,10 @@ const internalSetting = {
* @return {Promise} * @return {Promise}
*/ */
get: (access, data) => { get: (access, data) => {
return access.can('settings:get', data.id) return access
.can('settings:get', data.id)
.then(() => { .then(() => {
return settingModel return settingModel.query().where('id', data.id).first();
.query()
.where('id', data.id)
.first();
}) })
.then((row) => { .then((row) => {
if (row) { if (row) {
@ -102,12 +99,10 @@ const internalSetting = {
* @returns {*} * @returns {*}
*/ */
getCount: (access) => { getCount: (access) => {
return access.can('settings:list') return access
.can('settings:list')
.then(() => { .then(() => {
return settingModel return settingModel.query().count('id as count').first();
.query()
.count('id as count')
.first();
}) })
.then((row) => { .then((row) => {
return parseInt(row.count, 10); return parseInt(row.count, 10);
@ -121,13 +116,10 @@ const internalSetting = {
* @returns {Promise} * @returns {Promise}
*/ */
getAll: (access) => { getAll: (access) => {
return access.can('settings:list') return access.can('settings:list').then(() => {
.then(() => { return settingModel.query().orderBy('description', 'ASC');
return settingModel });
.query() },
.orderBy('description', 'ASC');
});
}
}; };
module.exports = internalSetting; module.exports = internalSetting;

View file

@ -1,24 +1,24 @@
const _ = require('lodash'); const _ = require('lodash');
const error = require('../lib/error'); const error = require('../lib/error');
const utils = require('../lib/utils'); const utils = require('../lib/utils');
const streamModel = require('../models/stream'); const streamModel = require('../models/stream');
const internalNginx = require('./nginx'); const internalNginx = require('./nginx');
const internalAuditLog = require('./audit-log'); const internalAuditLog = require('./audit-log');
function omissions () { function omissions() {
return ['is_deleted']; return ['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) => {
return access.can('streams:create', data) return access
.then((/*access_data*/) => { .can('streams:create', 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);
@ -26,26 +26,23 @@ const internalStream = {
data.meta = {}; data.meta = {};
} }
return streamModel return streamModel.query().insertAndFetch(data).then(utils.omitRow(omissions()));
.query()
.insertAndFetch(data)
.then(utils.omitRow(omissions()));
}) })
.then((row) => { .then((row) => {
// Configure nginx // Configure nginx
return internalNginx.configure(streamModel, 'stream', row) return internalNginx.configure(streamModel, 'stream', row).then(() => {
.then(() => { return internalStream.get(access, { id: row.id, expand: ['owner'] });
return internalStream.get(access, {id: row.id, expand: ['owner']}); });
});
}) })
.then((row) => { .then((row) => {
// Add to audit log // Add to audit log
return internalAuditLog.add(access, { return internalAuditLog
action: 'created', .add(access, {
object_type: 'stream', action: 'created',
object_id: row.id, object_type: 'stream',
meta: data object_id: row.id,
}) meta: data,
})
.then(() => { .then(() => {
return row; return row;
}); });
@ -59,10 +56,11 @@ const internalStream = {
* @return {Promise} * @return {Promise}
*/ */
update: (access, data) => { update: (access, data) => {
return access.can('streams:update', data.id) return access
.then((/*access_data*/) => { .can('streams:update', data.id)
.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: data.id}); return internalStream.get(access, { id: data.id });
}) })
.then((row) => { .then((row) => {
if (row.id !== data.id) { if (row.id !== data.id) {
@ -75,19 +73,19 @@ const internalStream = {
.patchAndFetchById(row.id, data) .patchAndFetchById(row.id, data)
.then(utils.omitRow(omissions())) .then(utils.omitRow(omissions()))
.then((saved_row) => { .then((saved_row) => {
return internalNginx.configure(streamModel, 'stream', saved_row) return internalNginx.configure(streamModel, 'stream', saved_row).then(() => {
.then(() => { return internalStream.get(access, { id: row.id, expand: ['owner'] });
return internalStream.get(access, {id: row.id, expand: ['owner']}); });
});
}) })
.then((saved_row) => { .then((saved_row) => {
// Add to audit log // Add to audit log
return internalAuditLog.add(access, { return internalAuditLog
action: 'updated', .add(access, {
object_type: 'stream', action: 'updated',
object_id: row.id, object_type: 'stream',
meta: data object_id: row.id,
}) meta: data,
})
.then(() => { .then(() => {
return saved_row; return saved_row;
}); });
@ -108,14 +106,10 @@ const internalStream = {
data = {}; data = {};
} }
return access.can('streams:get', data.id) return access
.can('streams:get', data.id)
.then((access_data) => { .then((access_data) => {
let query = streamModel const query = streamModel.query().where('is_deleted', 0).andWhere('id', data.id).allowGraph('[owner]').first();
.query()
.where('is_deleted', 0)
.andWhere('id', data.id)
.allowGraph('[owner]')
.first();
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));
@ -147,9 +141,10 @@ const internalStream = {
* @returns {Promise} * @returns {Promise}
*/ */
delete: (access, data) => { delete: (access, data) => {
return access.can('streams:delete', data.id) return access
.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) { if (!row) {
@ -160,22 +155,21 @@ const internalStream = {
.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) return internalNginx.deleteConfig('stream', row).then(() => {
.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()),
}); });
}); });
}) })
@ -192,11 +186,12 @@ const internalStream = {
* @returns {Promise} * @returns {Promise}
*/ */
enable: (access, data) => { enable: (access, data) => {
return access.can('streams:update', data.id) return access
.can('streams:update', data.id)
.then(() => { .then(() => {
return internalStream.get(access, { return internalStream.get(access, {
id: data.id, id: data.id,
expand: ['owner'] expand: ['owner'],
}); });
}) })
.then((row) => { .then((row) => {
@ -212,7 +207,7 @@ const internalStream = {
.query() .query()
.where('id', row.id) .where('id', row.id)
.patch({ .patch({
enabled: 1 enabled: 1,
}) })
.then(() => { .then(() => {
// Configure nginx // Configure nginx
@ -221,10 +216,10 @@ const internalStream = {
.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()),
}); });
}); });
}) })
@ -241,9 +236,10 @@ const internalStream = {
* @returns {Promise} * @returns {Promise}
*/ */
disable: (access, data) => { disable: (access, data) => {
return access.can('streams:update', data.id) return access
.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) { if (!row) {
@ -258,22 +254,21 @@ const internalStream = {
.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) return internalNginx.deleteConfig('stream', row).then(() => {
.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()),
}); });
}); });
}) })
@ -291,32 +286,26 @@ const internalStream = {
* @returns {Promise} * @returns {Promise}
*/ */
getAll: (access, expand, search_query) => { getAll: (access, expand, search_query) => {
return access.can('streams:list') return access.can('streams:list').then((access_data) => {
.then((access_data) => { const query = streamModel.query().where('is_deleted', 0).groupBy('id').allowGraph('[owner]').orderBy('incoming_port', 'ASC');
let query = streamModel
.query()
.where('is_deleted', 0)
.groupBy('id')
.allowGraph('[owner]')
.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') { if (typeof search_query === 'string') {
query.where(function () { query.where(function () {
this.where('incoming_port', 'like', '%' + search_query + '%'); this.where('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()));
}); });
}, },
/** /**
@ -327,20 +316,16 @@ const internalStream = {
* @returns {Promise} * @returns {Promise}
*/ */
getCount: (user_id, visibility) => { getCount: (user_id, visibility) => {
let query = streamModel const query = streamModel.query().count('id as count').where('is_deleted', 0);
.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() return query.first().then((row) => {
.then((row) => { return parseInt(row.count, 10);
return parseInt(row.count, 10); });
}); },
}
}; };
module.exports = internalStream; module.exports = internalStream;

View file

@ -1,12 +1,11 @@
const _ = require('lodash'); const _ = require('lodash');
const error = require('../lib/error'); const error = require('../lib/error');
const userModel = require('../models/user'); const userModel = require('../models/user');
const authModel = require('../models/auth'); const authModel = require('../models/auth');
const helpers = require('../lib/helpers'); const helpers = require('../lib/helpers');
const TokenModel = require('../models/token'); const TokenModel = require('../models/token');
module.exports = { module.exports = {
/** /**
* @param {Object} data * @param {Object} data
* @param {String} data.identity * @param {String} data.identity
@ -17,9 +16,9 @@ module.exports = {
* @returns {Promise} * @returns {Promise}
*/ */
getTokenFromEmail: (data, issuer) => { getTokenFromEmail: (data, issuer) => {
let Token = new TokenModel(); const Token = new TokenModel();
data.scope = data.scope || 'user'; data.scope = data.scope || 'user';
data.expiry = data.expiry || '1d'; data.expiry = data.expiry || '1d';
return userModel return userModel
@ -38,40 +37,37 @@ module.exports = {
.first() .first()
.then((auth) => { .then((auth) => {
if (auth) { if (auth) {
return auth.verifyPassword(data.secret) return auth.verifyPassword(data.secret).then((valid) => {
.then((valid) => { if (valid) {
if (valid) { if (data.scope !== 'user' && _.indexOf(user.roles, data.scope) === -1) {
// The scope requested doesn't exist as a role against the user,
if (data.scope !== 'user' && _.indexOf(user.roles, data.scope) === -1) { // you shall not pass.
// The scope requested doesn't exist as a role against the user, throw new error.AuthError('Invalid scope: ' + data.scope);
// you shall not pass.
throw new error.AuthError('Invalid scope: ' + data.scope);
}
// Create a moment of the expiry expression
let expiry = helpers.parseDatePeriod(data.expiry);
if (expiry === null) {
throw new error.AuthError('Invalid expiry time: ' + data.expiry);
}
return Token.create({
iss: issuer || 'api',
attrs: {
id: user.id
},
scope: [data.scope],
expiresIn: data.expiry
})
.then((signed) => {
return {
token: signed.token,
expires: expiry.toISOString()
};
});
} else {
throw new error.AuthError('Invalid password');
} }
});
// Create a moment of the expiry expression
const expiry = helpers.parseDatePeriod(data.expiry);
if (expiry === null) {
throw new error.AuthError('Invalid expiry time: ' + data.expiry);
}
return Token.create({
iss: issuer || 'api',
attrs: {
id: user.id,
},
scope: [data.scope],
expiresIn: data.expiry,
}).then((signed) => {
return {
token: signed.token,
expires: expiry.toISOString(),
};
});
} else {
throw new error.AuthError('Invalid password');
}
});
} else { } else {
throw new error.AuthError('No password auth for user'); throw new error.AuthError('No password auth for user');
} }
@ -90,21 +86,20 @@ module.exports = {
* @returns {Promise} * @returns {Promise}
*/ */
getFreshToken: (access, data) => { getFreshToken: (access, data) => {
let Token = new TokenModel(); const Token = new TokenModel();
data = data || {}; data = data || {};
data.expiry = data.expiry || '1d'; data.expiry = data.expiry || '1d';
if (access && access.token.getUserId(0)) { if (access && access.token.getUserId(0)) {
// Create a moment of the expiry expression // Create a moment of the expiry expression
let expiry = helpers.parseDatePeriod(data.expiry); const expiry = helpers.parseDatePeriod(data.expiry);
if (expiry === null) { if (expiry === null) {
throw new error.AuthError('Invalid expiry time: ' + data.expiry); throw new error.AuthError('Invalid expiry time: ' + data.expiry);
} }
let token_attrs = { const 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
@ -118,17 +113,16 @@ module.exports = {
} }
return Token.create({ return Token.create({
iss: 'api', iss: 'api',
scope: scope, scope,
attrs: token_attrs, attrs: token_attrs,
expiresIn: data.expiry expiresIn: data.expiry,
}) }).then((signed) => {
.then((signed) => { return {
return { token: signed.token,
token: signed.token, expires: expiry.toISOString(),
expires: expiry.toISOString() };
}; });
});
} else { } else {
throw new error.AssertionFailedError('Existing token contained invalid user data'); throw new error.AssertionFailedError('Existing token contained invalid user data');
} }
@ -140,23 +134,22 @@ module.exports = {
*/ */
getTokenFromUser: (user) => { getTokenFromUser: (user) => {
const expire = '1d'; const expire = '1d';
const Token = new TokenModel(); const Token = new TokenModel();
const expiry = helpers.parseDatePeriod(expire); const expiry = helpers.parseDatePeriod(expire);
return 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) => {
.then((signed) => { return {
return { token: signed.token,
token: signed.token, expires: expiry.toISOString(),
expires: expiry.toISOString(), user,
user: user };
}; });
}); },
}
}; };

View file

@ -1,43 +1,40 @@
const _ = require('lodash'); const _ = require('lodash');
const error = require('../lib/error'); const error = require('../lib/error');
const utils = require('../lib/utils'); const utils = require('../lib/utils');
const userModel = require('../models/user'); const userModel = require('../models/user');
const userPermissionModel = require('../models/user_permission'); const userPermissionModel = require('../models/user_permission');
const authModel = require('../models/auth'); const authModel = require('../models/auth');
const gravatar = require('gravatar'); const gravatar = require('gravatar');
const internalToken = require('./token'); const internalToken = require('./token');
const internalAuditLog = require('./audit-log'); const internalAuditLog = require('./audit-log');
function omissions () { function omissions() {
return ['is_deleted']; return ['is_deleted'];
} }
const internalUser = { const internalUser = {
/** /**
* @param {Access} access * @param {Access} access
* @param {Object} data * @param {Object} data
* @returns {Promise} * @returns {Promise}
*/ */
create: (access, data) => { create: (access, data) => {
let auth = data.auth || null; const 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;
} }
return access.can('users:create', data) return access
.can('users:create', data)
.then(() => { .then(() => {
data.avatar = gravatar.url(data.email, {default: 'mm'}); data.avatar = gravatar.url(data.email, { default: 'mm' });
return userModel return userModel.query().insertAndFetch(data).then(utils.omitRow(omissions()));
.query()
.insertAndFetch(data)
.then(utils.omitRow(omissions()));
}) })
.then((user) => { .then((user) => {
if (auth) { if (auth) {
@ -45,9 +42,9 @@ const internalUser = {
.query() .query()
.insert({ .insert({
user_id: user.id, user_id: user.id,
type: auth.type, type: auth.type,
secret: auth.secret, secret: auth.secret,
meta: {} meta: {},
}) })
.then(() => { .then(() => {
return user; return user;
@ -58,32 +55,33 @@ const internalUser = {
}) })
.then((user) => { .then((user) => {
// Create permissions row as well // Create permissions row as well
let is_admin = data.roles.indexOf('admin') !== -1; const is_admin = data.roles.indexOf('admin') !== -1;
return userPermissionModel return userPermissionModel
.query() .query()
.insert({ .insert({
user_id: user.id, user_id: user.id,
visibility: is_admin ? 'all' : 'user', visibility: is_admin ? 'all' : 'user',
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',
}) })
.then(() => { .then(() => {
return internalUser.get(access, {id: user.id, expand: ['permissions']}); return internalUser.get(access, { id: user.id, expand: ['permissions'] });
}); });
}) })
.then((user) => { .then((user) => {
// Add to audit log // Add to audit log
return internalAuditLog.add(access, { return internalAuditLog
action: 'created', .add(access, {
object_type: 'user', action: 'created',
object_id: user.id, object_type: 'user',
meta: user object_id: user.id,
}) meta: user,
})
.then(() => { .then(() => {
return user; return user;
}); });
@ -103,33 +101,30 @@ const internalUser = {
data.is_disabled = data.is_disabled ? 1 : 0; data.is_disabled = data.is_disabled ? 1 : 0;
} }
return access.can('users:update', data.id) return access
.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}) return internalUser.get(access, { id: data.id }).then((user) => {
.then((user) => { // 2. if email is to be changed, find other users with that email
if (typeof data.email !== 'undefined') {
data.email = data.email.toLowerCase().trim();
// 2. if email is to be changed, find other users with that email if (user.email !== data.email) {
if (typeof data.email !== 'undefined') { return internalUser.isEmailAvailable(data.email, data.id).then((available) => {
data.email = data.email.toLowerCase().trim(); if (!available) {
throw new error.ValidationError('Email address already in use - ' + data.email);
}
if (user.email !== data.email) { return user;
return internalUser.isEmailAvailable(data.email, data.id) });
.then((available) => {
if (!available) {
throw new error.ValidationError('Email address already in use - ' + data.email);
}
return user;
});
}
} }
}
// No change to email: // No change to email:
return user; return user;
}); });
}) })
.then((user) => { .then((user) => {
if (user.id !== data.id) { if (user.id !== data.id) {
@ -137,24 +132,22 @@ const internalUser = {
throw new error.InternalValidationError('User could not be updated, IDs do not match: ' + user.id + ' !== ' + data.id); throw new error.InternalValidationError('User could not be updated, IDs do not match: ' + user.id + ' !== ' + data.id);
} }
data.avatar = gravatar.url(data.email || user.email, {default: 'mm'}); data.avatar = gravatar.url(data.email || user.email, { default: 'mm' });
return userModel return userModel.query().patchAndFetchById(user.id, data).then(utils.omitRow(omissions()));
.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.add(access, { return internalAuditLog
action: 'updated', .add(access, {
object_type: 'user', action: 'updated',
object_id: user.id, object_type: 'user',
meta: data object_id: user.id,
}) meta: data,
})
.then(() => { .then(() => {
return user; return user;
}); });
@ -178,14 +171,10 @@ const internalUser = {
data.id = access.token.getUserId(0); data.id = access.token.getUserId(0);
} }
return access.can('users:get', data.id) return access
.can('users:get', data.id)
.then(() => { .then(() => {
let query = userModel const query = userModel.query().where('is_deleted', 0).andWhere('id', data.id).allowGraph('[permissions]').first();
.query()
.where('is_deleted', 0)
.andWhere('id', data.id)
.allowGraph('[permissions]')
.first();
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(', ') + ']');
@ -213,20 +202,15 @@ const internalUser = {
* @param user_id * @param user_id
*/ */
isEmailAvailable: (email, user_id) => { isEmailAvailable: (email, user_id) => {
let query = userModel const query = userModel.query().where('email', '=', email.toLowerCase().trim()).where('is_deleted', 0).first();
.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 return query.then((user) => {
.then((user) => { return !user;
return !user; });
});
}, },
/** /**
@ -237,9 +221,10 @@ const internalUser = {
* @returns {Promise} * @returns {Promise}
*/ */
delete: (access, data) => { delete: (access, data) => {
return access.can('users:delete', data.id) return access
.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) {
@ -255,15 +240,15 @@ const internalUser = {
.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()),
}); });
}); });
}) })
@ -280,19 +265,15 @@ const internalUser = {
* @returns {*} * @returns {*}
*/ */
getCount: (access, search_query) => { getCount: (access, search_query) => {
return access.can('users:list') return access
.can('users:list')
.then(() => { .then(() => {
let query = userModel const query = userModel.query().count('id as count').where('is_deleted', 0).first();
.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 + '%') this.where('user.name', 'like', '%' + search_query + '%').orWhere('user.email', 'like', '%' + search_query + '%');
.orWhere('user.email', 'like', '%' + search_query + '%');
}); });
} }
@ -312,29 +293,22 @@ const internalUser = {
* @returns {Promise} * @returns {Promise}
*/ */
getAll: (access, expand, search_query) => { getAll: (access, expand, search_query) => {
return access.can('users:list') return access.can('users:list').then(() => {
.then(() => { const query = userModel.query().where('is_deleted', 0).groupBy('id').allowGraph('[permissions]').orderBy('name', 'ASC');
let query = userModel
.query()
.where('is_deleted', 0)
.groupBy('id')
.allowGraph('[permissions]')
.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 + '%') this.where('name', 'like', '%' + search_query + '%').orWhere('email', '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(', ') + ']');
} }
return query.then(utils.omitRows(omissions())); return query.then(utils.omitRows(omissions()));
}); });
}, },
/** /**
@ -361,9 +335,10 @@ const internalUser = {
* @return {Promise} * @return {Promise}
*/ */
setPassword: (access, data) => { setPassword: (access, data) => {
return access.can('users:password', data.id) return access
.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) {
@ -377,10 +352,11 @@ const internalUser = {
throw new error.ValidationError('Current password was not supplied'); throw new error.ValidationError('Current password was not supplied');
} }
return internalToken.getTokenFromEmail({ return internalToken
identity: user.email, .getTokenFromEmail({
secret: data.current identity: user.email,
}) secret: data.current,
})
.then(() => { .then(() => {
return user; return user;
}); });
@ -398,37 +374,31 @@ const internalUser = {
.then((existing_auth) => { .then((existing_auth) => {
if (existing_auth) { if (existing_auth) {
// patch // patch
return authModel return authModel.query().where('user_id', user.id).andWhere('type', data.type).patch({
.query() type: data.type, // This is required for the model to encrypt on save
.where('user_id', user.id) secret: data.secret,
.andWhere('type', data.type) });
.patch({
type: data.type, // This is required for the model to encrypt on save
secret: data.secret
});
} else { } else {
// insert // insert
return authModel return authModel.query().insert({
.query() user_id: user.id,
.insert({ type: data.type,
user_id: user.id, secret: data.secret,
type: data.type, meta: {},
secret: data.secret, });
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,
} },
}); });
}); });
}) })
@ -443,9 +413,10 @@ const internalUser = {
* @return {Promise} * @return {Promise}
*/ */
setPermissions: (access, data) => { setPermissions: (access, data) => {
return access.can('users:permissions', data.id) return access
.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) {
@ -467,26 +438,23 @@ const internalUser = {
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 { } else {
// insert // insert
return userPermissionModel return userPermissionModel.query().insertAndFetch(_.assign({ user_id: user.id }, data));
.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,
} },
}); });
}); });
}) })
.then(() => { .then(() => {
@ -500,14 +468,15 @@ const internalUser = {
* @param {Integer} data.id * @param {Integer} data.id
*/ */
loginAs: (access, data) => { loginAs: (access, data) => {
return access.can('users:loginas', data.id) return access
.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);
}); });
} },
}; };
module.exports = internalUser; module.exports = internalUser;

View file

@ -1,19 +1,19 @@
module.exports = { module.exports = {
development: { development: {
client: 'mysql', client: 'mysql',
migrations: { migrations: {
tableName: 'migrations', tableName: 'migrations',
stub: 'lib/migrate_template.js', stub: 'lib/migrate_template.js',
directory: 'migrations' directory: 'migrations',
} },
}, },
production: { production: {
client: 'mysql', client: 'mysql',
migrations: { migrations: {
tableName: 'migrations', tableName: 'migrations',
stub: 'lib/migrate_template.js', stub: 'lib/migrate_template.js',
directory: 'migrations' directory: 'migrations',
} },
} },
}; };

View file

@ -8,24 +8,24 @@
* *
*/ */
const _ = require('lodash'); const _ = require('lodash');
const logger = require('../logger').access; const logger = require('../logger').access;
const validator = require('ajv'); const validator = require('ajv');
const error = require('./error'); const error = require('./error');
const userModel = require('../models/user'); const userModel = require('../models/user');
const proxyHostModel = require('../models/proxy_host'); const proxyHostModel = require('../models/proxy_host');
const TokenModel = require('../models/token'); const TokenModel = require('../models/token');
const roleSchema = require('./access/roles.json'); const roleSchema = require('./access/roles.json');
const permsSchema = require('./access/permissions.json'); const permsSchema = require('./access/permissions.json');
module.exports = function (token_string) { module.exports = function (token_string) {
let Token = new TokenModel(); const Token = new TokenModel();
let token_data = null; let token_data = null;
let initialized = false; let initialized = false;
let object_cache = {}; const object_cache = {};
let allow_internal_access = false; let allow_internal_access = false;
let user_roles = []; let user_roles = [];
let permissions = {}; let permissions = {};
/** /**
* Loads the Token object from the token string * Loads the Token object from the token string
@ -39,8 +39,8 @@ module.exports = function (token_string) {
} else if (!token_string) { } else if (!token_string) {
reject(new error.PermissionError('Permission Denied')); reject(new error.PermissionError('Permission Denied'));
} else { } else {
resolve(Token.load(token_string) resolve(
.then((data) => { Token.load(token_string).then((data) => {
token_data = data; token_data = data;
// 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:
@ -75,10 +75,9 @@ module.exports = function (token_string) {
throw new error.AuthError('Invalid token scope for User'); throw new error.AuthError('Invalid token scope for User');
} else { } else {
initialized = true; initialized = true;
user_roles = user.roles; user_roles = user.roles;
permissions = user.permissions; permissions = user.permissions;
} }
} else { } else {
throw new error.AuthError('User cannot be loaded for Token'); throw new error.AuthError('User cannot be loaded for Token');
} }
@ -86,7 +85,8 @@ module.exports = function (token_string) {
} else { } else {
initialized = true; initialized = true;
} }
})); }),
);
} }
}); });
}; };
@ -105,49 +105,45 @@ module.exports = function (token_string) {
if (typeof token_data.attrs.id === 'undefined' || !token_data.attrs.id) { if (typeof token_data.attrs.id === 'undefined' || !token_data.attrs.id) {
reject(new error.AuthError('User Token supplied without a User ID')); reject(new error.AuthError('User Token supplied without a User ID'));
} else { } else {
let token_user_id = token_data.attrs.id ? token_data.attrs.id : 0; const token_user_id = token_data.attrs.id ? token_data.attrs.id : 0;
let query; let query;
if (typeof object_cache[object_type] === 'undefined') { if (typeof object_cache[object_type] === 'undefined') {
switch (object_type) { switch (object_type) {
// USERS - should only return yourself
// USERS - should only return yourself case 'users':
case 'users': resolve(token_user_id ? [token_user_id] : []);
resolve(token_user_id ? [token_user_id] : []); break;
break;
// Proxy Hosts // Proxy Hosts
case 'proxy_hosts': case 'proxy_hosts':
query = proxyHostModel query = proxyHostModel.query().select('id').andWhere('is_deleted', 0);
.query()
.select('id')
.andWhere('is_deleted', 0);
if (permissions.visibility === 'user') { if (permissions.visibility === 'user') {
query.andWhere('owner_user_id', token_user_id); query.andWhere('owner_user_id', token_user_id);
} }
resolve(query resolve(
.then((rows) => { query.then((rows) => {
let result = []; const result = [];
_.forEach(rows, (rule_row) => { _.forEach(rows, (rule_row) => {
result.push(rule_row.id); result.push(rule_row.id);
}); });
// enum should not have less than 1 item // enum should not have less than 1 item
if (!result.length) { if (!result.length) {
result.push(0); result.push(0);
} }
return result; return result;
}) }),
); );
break; break;
// DEFAULT: null // DEFAULT: null
default: default:
resolve(null); resolve(null);
break; break;
} }
} else { } else {
resolve(object_cache[object_type]); resolve(object_cache[object_type]);
@ -156,11 +152,10 @@ module.exports = function (token_string) {
} else { } else {
resolve(null); resolve(null);
} }
}) }).then((objects) => {
.then((objects) => { object_cache[object_type] = objects;
object_cache[object_type] = objects; return objects;
return objects; });
});
}; };
/** /**
@ -170,51 +165,49 @@ module.exports = function (token_string) {
* @returns {Object} * @returns {Object}
*/ */
this.getObjectSchema = (permission_label) => { this.getObjectSchema = (permission_label) => {
let base_object_type = permission_label.split(':').shift(); const base_object_type = permission_label.split(':').shift();
let schema = { const schema = {
$id: 'objects', $id: 'objects',
$schema: 'http://json-schema.org/draft-07/schema#', $schema: 'http://json-schema.org/draft-07/schema#',
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') + '$',
} },
} },
}; };
return this.loadObjects(base_object_type) return this.loadObjects(base_object_type).then((object_result) => {
.then((object_result) => { if (typeof object_result === 'object' && object_result !== null) {
if (typeof object_result === 'object' && object_result !== null) { schema.properties[base_object_type] = {
schema.properties[base_object_type] = { type: 'number',
type: 'number', enum: object_result,
enum: object_result, minimum: 1,
minimum: 1 };
}; } else {
} else { schema.properties[base_object_type] = {
schema.properties[base_object_type] = { type: 'number',
type: 'number', minimum: 1,
minimum: 1 };
}; }
}
return schema; return schema;
}); });
}; };
return { return {
token: Token, token: Token,
/** /**
@ -223,7 +216,7 @@ module.exports = function (token_string) {
* @returns {Promise} * @returns {Promise}
*/ */
load: (allow_internal) => { load: (allow_internal) => {
return new Promise(function (resolve/*, reject*/) { return new Promise(function (resolve /*, reject */) {
if (token_string) { if (token_string) {
resolve(Token.load(token_string)); resolve(Token.load(token_string));
} else { } else {
@ -244,71 +237,64 @@ module.exports = function (token_string) {
can: (permission, data) => { can: (permission, data) => {
if (allow_internal_access === true) { if (allow_internal_access === true) {
return Promise.resolve(true); return Promise.resolve(true);
//return true; // return true;
} else { } else {
return this.init() return this.init()
.then(() => { .then(() => {
// initialized, token decoded ok // initialized, token decoded ok
return this.getObjectSchema(permission) return this.getObjectSchema(permission).then((objectSchema) => {
.then((objectSchema) => { const data_schema = {
let data_schema = { [permission]: {
[permission]: { data,
data: data, scope: Token.get('scope'),
scope: Token.get('scope'), roles: user_roles,
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 },
} };
};
let permissionSchema = { const permissionSchema = {
$schema: 'http://json-schema.org/draft-07/schema#', $schema: 'http://json-schema.org/draft-07/schema#',
$async: true, $async: true,
$id: 'permissions', $id: 'permissions',
additionalProperties: false, additionalProperties: false,
properties: {} properties: {},
}; };
permissionSchema.properties[permission] = require('./access/' + permission.replace(/:/gim, '-') + '.json'); permissionSchema.properties[permission] = require('./access/' + permission.replace(/:/gim, '-') + '.json');
// logger.info('objectSchema', JSON.stringify(objectSchema, null, 2)); // logger.info('objectSchema', JSON.stringify(objectSchema, null, 2));
// logger.info('permissionSchema', JSON.stringify(permissionSchema, null, 2)); // logger.info('permissionSchema', JSON.stringify(permissionSchema, null, 2));
// logger.info('data_schema', JSON.stringify(data_schema, null, 2)); // logger.info('data_schema', JSON.stringify(data_schema, null, 2));
let ajv = validator({ const ajv = validator({
verbose: true, verbose: true,
allErrors: true, allErrors: true,
format: 'full', format: 'full',
missingRefs: 'fail', missingRefs: 'fail',
breakOnError: true, breakOnError: true,
coerceTypes: true, coerceTypes: true,
schemas: [ schemas: [roleSchema, permsSchema, objectSchema, permissionSchema],
roleSchema,
permsSchema,
objectSchema,
permissionSchema
]
});
return ajv.validate('permissions', data_schema)
.then(() => {
return data_schema[permission];
});
}); });
return ajv.validate('permissions', data_schema).then(() => {
return data_schema[permission];
});
});
}) })
.catch((err) => { .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 new error.PermissionError('Permission Denied', err); throw new error.PermissionError('Permission Denied', err);
}); });
} }
} },
}; };
}; };

View file

@ -1,11 +1,10 @@
const dnsPlugins = require('../certbot-dns-plugins.json'); const dnsPlugins = require('../certbot-dns-plugins.json');
const utils = require('./utils'); const utils = require('./utils');
const error = require('./error'); const error = require('./error');
const logger = require('../logger').certbot; const logger = require('../logger').certbot;
const batchflow = require('batchflow'); const batchflow = require('batchflow');
const certbot = { const certbot = {
/** /**
* @param {array} pluginKeys * @param {array} pluginKeys
*/ */
@ -18,9 +17,11 @@ const certbot = {
return; return;
} }
batchflow(pluginKeys).sequential() batchflow(pluginKeys)
.sequential()
.each((i, pluginKey, next) => { .each((i, pluginKey, next) => {
certbot.installPlugin(pluginKey) certbot
.installPlugin(pluginKey)
.then(() => { .then(() => {
next(); next();
}) })
@ -59,7 +60,8 @@ const certbot = {
logger.start(`Installing ${pluginKey}...`); logger.start(`Installing ${pluginKey}...`);
const cmd = 'pip install --no-cache-dir ' + plugin.package_name; const cmd = 'pip install --no-cache-dir ' + plugin.package_name;
return utils.exec(cmd) return utils
.exec(cmd)
.then((result) => { .then((result) => {
logger.complete(`Installed ${pluginKey}`); logger.complete(`Installed ${pluginKey}`);
return result; return result;

View file

@ -1,6 +1,6 @@
const fs = require('fs'); const fs = require('fs');
const NodeRSA = require('node-rsa'); const NodeRSA = require('node-rsa');
const logger = require('../logger').global; const logger = require('../logger').global;
const keysFile = '/data/etc/npm/keys.json'; const keysFile = '/data/etc/npm/keys.json';
@ -14,13 +14,13 @@ const configure = () => {
let configData; let configData;
try { try {
configData = require(filename); configData = require(filename);
} catch (err) { } catch {
// do nothing // do nothing
} }
if (configData && 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();
return; return;
} }
@ -29,20 +29,20 @@ const configure = () => {
const envMysqlHost = process.env.DB_MYSQL_HOST || null; const envMysqlHost = process.env.DB_MYSQL_HOST || null;
const envMysqlUser = process.env.DB_MYSQL_USER || null; const envMysqlUser = process.env.DB_MYSQL_USER || null;
const envMysqlName = process.env.DB_MYSQL_NAME || null; const envMysqlName = process.env.DB_MYSQL_NAME || null;
const envMysqlTls = process.env.DB_MYSQL_TLS || null; const envMysqlTls = process.env.DB_MYSQL_TLS || null;
const envMysqlCa = process.env.DB_MYSQL_CA || '/etc/ssl/certs/ca-certificates.crt'; const envMysqlCa = process.env.DB_MYSQL_CA || '/etc/ssl/certs/ca-certificates.crt';
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: 'mysql', engine: 'mysql',
host: envMysqlHost, host: envMysqlHost,
port: process.env.DB_MYSQL_PORT || 3306, port: process.env.DB_MYSQL_PORT || 3306,
user: envMysqlUser, user: envMysqlUser,
password: process.env.DB_MYSQL_PASSWORD, password: process.env.DB_MYSQL_PASSWORD,
name: envMysqlName, name: envMysqlName,
ssl: envMysqlTls ? { ca: fs.readFileSync(envMysqlCa) } : false, ssl: envMysqlTls ? { ca: fs.readFileSync(envMysqlCa) } : false,
}, },
keys: getKeys(), keys: getKeys(),
}; };
@ -54,13 +54,13 @@ const configure = () => {
instance = { instance = {
database: { database: {
engine: 'knex-native', engine: 'knex-native',
knex: { knex: {
client: 'sqlite3', client: 'sqlite3',
connection: { connection: {
filename: envSqliteFile filename: envSqliteFile,
}, },
useNullAsDefault: true useNullAsDefault: true,
} },
}, },
keys: getKeys(), keys: getKeys(),
}; };
@ -103,18 +103,17 @@ const generateKeys = () => {
}; };
module.exports = { module.exports = {
/** /**
* *
* @param {string} key ie: 'database' or 'database.engine' * @param {string} key ie: 'database' or 'database.engine'
* @returns {boolean} * @returns {boolean}
*/ */
has: function(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 {
@ -183,5 +182,5 @@ module.exports = {
*/ */
useLetsencryptStaging: function () { useLetsencryptStaging: function () {
return !!process.env.LE_STAGING; return !!process.env.LE_STAGING;
} },
}; };

View file

@ -1,96 +1,95 @@
const _ = require('lodash'); const _ = require('lodash');
const util = require('util'); const util = require('util');
module.exports = { module.exports = {
PermissionError: function (message, 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;
}, },
ItemNotFoundError: function (id, previous) { ItemNotFoundError: function (id, 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 = 'Item Not Found - ' + id; this.message = 'Item Not Found - ' + id;
this.public = true; this.public = true;
this.status = 404; this.status = 404;
}, },
AuthError: function (message, 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.public = true; this.public = true;
this.status = 401; this.status = 401;
}, },
InternalError: function (message, previous) { InternalError: 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.status = 500; this.status = 500;
this.public = false; this.public = false;
}, },
InternalValidationError: function (message, previous) { InternalValidationError: 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.status = 400; this.status = 400;
this.public = false; this.public = false;
}, },
ConfigurationError: function (message, previous) { ConfigurationError: 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.status = 400; this.status = 400;
this.public = true; this.public = true;
}, },
CacheError: function (message, previous) { CacheError: function (message, previous) {
Error.captureStackTrace(this, this.constructor); Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name; this.name = this.constructor.name;
this.message = message; this.message = message;
this.previous = previous; this.previous = previous;
this.status = 500; this.status = 500;
this.public = false; this.public = false;
}, },
ValidationError: function (message, previous) { ValidationError: 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.public = true; this.public = true;
this.status = 400; this.status = 400;
}, },
AssertionFailedError: function (message, previous) { AssertionFailedError: 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.public = false; this.public = false;
this.status = 400; this.status = 400;
}, },
CommandError: function (stdErr, code, previous) { CommandError: function (stdErr, code, 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 = stdErr; this.message = stdErr;
this.code = code; this.code = code;
this.public = false; this.public = false;
}, },
}; };

View file

@ -1,40 +1,36 @@
const validator = require('../validator'); const validator = require('../validator');
module.exports = function (req, res, next) { module.exports = function (req, res, next) {
if (req.headers.origin) { if (req.headers.origin) {
const originSchema = { const originSchema = {
oneOf: [ oneOf: [
{ {
type: 'string', type: 'string',
pattern: '^[a-z\\-]+:\\/\\/(?:[\\w\\-\\.]+(:[0-9]+)?/?)?$' pattern: '^[a-z\\-]+:\\/\\/(?:[\\w\\-\\.]+(:[0-9]+)?/?)?$',
}, },
{ {
type: 'string', type: 'string',
pattern: '^[a-z\\-]+:\\/\\/(?:\\[([a-z0-9]{0,4}\\:?)+\\])?/?(:[0-9]+)?$' pattern: '^[a-z\\-]+:\\/\\/(?:\\[([a-z0-9]{0,4}\\:?)+\\])?/?(:[0-9]+)?$',
} },
] ],
}; };
// very relaxed validation.... // very relaxed validation....
validator(originSchema, req.headers.origin) validator(originSchema, req.headers.origin)
.then(function () { .then(function () {
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': 'Content-Type, Cache-Control, Pragma, Expires, Authorization, X-Dataset-Total, X-Dataset-Offset, X-Dataset-Limit', 'Access-Control-Allow-Headers': '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();
}) })
.catch(next); .catch(next);
} else { } else {
// No origin // No origin
next(); next();
} }
}; };

View file

@ -3,8 +3,9 @@ const Access = require('../access');
module.exports = () => { module.exports = () => {
return function (req, res, next) { return function (req, res, next) {
res.locals.access = null; res.locals.access = null;
let access = new Access(res.locals.token || null); const access = new Access(res.locals.token || null);
access.load() access
.load()
.then(() => { .then(() => {
res.locals.access = access; res.locals.access = access;
next(); next();
@ -12,4 +13,3 @@ module.exports = () => {
.catch(next); .catch(next);
}; };
}; };

View file

@ -1,7 +1,7 @@
module.exports = function () { module.exports = function () {
return function (req, res, next) { return function (req, res, next) {
if (req.headers.authorization) { if (req.headers.authorization) {
let parts = req.headers.authorization.split(' '); const 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];

View file

@ -1,7 +1,6 @@
let _ = require('lodash'); const _ = require('lodash');
module.exports = function (default_sort, default_offset, default_limit, max_limit) { module.exports = function (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
* *
@ -12,33 +11,32 @@ module.exports = function (default_sort, default_offset, default_limit, max_limi
*/ */
return function (req, res, next) { return function (req, res, next) {
req.query.offset = typeof req.query.limit === 'undefined' ? default_offset || 0 : parseInt(req.query.offset, 10); req.query.offset = typeof req.query.limit === 'undefined' ? default_offset || 0 : parseInt(req.query.offset, 10);
req.query.limit = typeof req.query.limit === 'undefined' ? default_limit || 50 : parseInt(req.query.limit, 10); req.query.limit = typeof req.query.limit === 'undefined' ? default_limit || 50 : 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;
let myRegexp = /.*\.(asc|desc)$/ig; const myRegexp = /.*\.(asc|desc)$/gi;
let sort_array = []; const sort_array = [];
sort = sort.split(','); sort = sort.split(',');
_.map(sort, function (val) { _.map(sort, function (val) {
let matches = myRegexp.exec(val); const matches = myRegexp.exec(val);
if (matches !== null) { if (matches !== null) {
let dir = matches[1]; const 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,7 +1,6 @@
const moment = require('moment'); const moment = require('moment');
module.exports = { 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
* *
@ -21,12 +20,11 @@ module.exports = {
* @returns {Object} * @returns {Object}
*/ */
parseDatePeriod: function (expression) { parseDatePeriod: function (expression) {
let matches = expression.match(/^([0-9]+)(y|Q|M|w|d|h|m|s|ms)$/m); const 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;
} },
}; };

View file

@ -1,5 +1,5 @@
const migrate_name = 'identifier_for_migrate'; const migrate_name = 'identifier_for_migrate';
const logger = require('../logger').migrate; const logger = require('../logger').migrate;
/** /**
* Migrate * Migrate
@ -11,12 +11,11 @@ const logger = require('../logger').migrate;
* @returns {Promise} * @returns {Promise}
*/ */
exports.up = function (knex, Promise) { exports.up = function (knex, Promise) {
logger.info('[' + migrate_name + '] 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();
@ -25,7 +24,7 @@ exports.up = function (knex, Promise) {
}) })
.then(function () { .then(function () {
logger.info('[' + migrate_name + '] Notification Table created'); logger.info('[' + migrate_name + '] Notification Table created');
});*/ }); */
logger.info('[' + migrate_name + '] Migrating Up Complete'); logger.info('[' + migrate_name + '] Migrating Up Complete');
@ -44,10 +43,10 @@ exports.down = function (knex, Promise) {
// Drop table example: // Drop table example:
/*return knex.schema.dropTable('notification') /* return knex.schema.dropTable('notification')
.then(() => { .then(() => {
logger.info('[' + migrate_name + '] Notification Table dropped'); logger.info('[' + migrate_name + '] Notification Table dropped');
});*/ }); */
logger.info('[' + migrate_name + '] Migrating Down Complete'); logger.info('[' + migrate_name + '] Migrating Down Complete');

View file

@ -1,19 +1,17 @@
const _ = require('lodash'); const _ = require('lodash');
const exec = require('child_process').exec; const exec = require('child_process').exec;
const spawn = require('child_process').spawn; const spawn = require('child_process').spawn;
const execFile = require('child_process').execFile; const execFile = require('child_process').execFile;
const { Liquid } = require('liquidjs'); const { Liquid } = require('liquidjs');
const error = require('./error'); const error = require('./error');
//const logger = require('../logger').global; // const logger = require('../logger').global;
module.exports = { module.exports = {
/** /**
* @param {String} cmd * @param {String} cmd
*/ */
exec: async function(cmd, options = {}) { exec: async function (cmd, options = {}) {
//logger.debug('CMD:', cmd); // logger.debug('CMD:', cmd);
const { stdout, stderr } = await new Promise((resolve, reject) => { const { stdout, stderr } = await new Promise((resolve, reject) => {
const child = exec(cmd, options, (isError, stdout, stderr) => { const child = exec(cmd, options, (isError, stdout, stderr) => {
@ -36,7 +34,7 @@ module.exports = {
* @param {Array} args * @param {Array} args
*/ */
execFile: async function (cmd, args, options = {}) { execFile: async function (cmd, args, options = {}) {
//logger.debug('CMD: ' + cmd + ' ' + (args ? args.join(' ') : '')); // logger.debug('CMD: ' + cmd + ' ' + (args ? args.join(' ') : ''));
const { stdout, stderr } = await new Promise((resolve, reject) => { const { stdout, stderr } = await new Promise((resolve, reject) => {
const child = execFile(cmd, args, options, (isError, stdout, stderr) => { const child = execFile(cmd, args, options, (isError, stdout, stderr) => {
@ -60,9 +58,9 @@ module.exports = {
execfg: function (cmd) { execfg: function (cmd) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const childProcess = spawn(cmd, { const childProcess = spawn(cmd, {
shell: true, shell: true,
detached: true, detached: true,
stdio: 'inherit' stdio: 'inherit',
}); });
childProcess.on('error', (err) => { childProcess.on('error', (err) => {
@ -119,7 +117,7 @@ module.exports = {
*/ */
getRenderEngine: function () { getRenderEngine: function () {
const renderEngine = new Liquid({ const renderEngine = new Liquid({
root: __dirname + '/../templates/' root: __dirname + '/../templates/',
}); });
/** /**
@ -136,5 +134,5 @@ module.exports = {
}); });
return renderEngine; return renderEngine;
} },
}; };

View file

@ -1,13 +1,13 @@
const error = require('../error'); const error = require('../error');
const path = require('path'); const path = require('path');
const parser = require('@apidevtools/json-schema-ref-parser'); const parser = require('@apidevtools/json-schema-ref-parser');
const ajv = require('ajv')({ const ajv = require('ajv')({
verbose: true, verbose: true,
validateSchema: true, validateSchema: true,
allErrors: false, allErrors: false,
format: 'full', format: 'full',
coerceTypes: true coerceTypes: true,
}); });
/** /**
@ -15,31 +15,29 @@ const ajv = require('ajv')({
* @param {Object} payload * @param {Object} payload
* @returns {Promise} * @returns {Promise}
*/ */
function apiValidator (schema, payload/*, description*/) { function apiValidator(schema, payload /*, description */) {
return new Promise(function Promise_apiValidator (resolve, reject) { return new Promise(function Promise_apiValidator(resolve, reject) {
if (typeof payload === 'undefined') { if (typeof payload === 'undefined') {
reject(new error.ValidationError('Payload is undefined')); reject(new error.ValidationError('Payload is undefined'));
} }
let validate = ajv.compile(schema); const validate = ajv.compile(schema);
let valid = validate(payload); const valid = validate(payload);
if (valid && !validate.errors) { if (valid && !validate.errors) {
resolve(payload); resolve(payload);
} else { } else {
let message = ajv.errorsText(validate.errors); const message = ajv.errorsText(validate.errors);
let err = new error.ValidationError(message); const err = new error.ValidationError(message);
err.debug = [validate.errors, payload]; err.debug = [validate.errors, payload];
reject(err); reject(err);
} }
}); });
} }
apiValidator.loadSchemas = parser apiValidator.loadSchemas = parser.dereference(path.resolve('schema/index.json')).then((schema) => {
.dereference(path.resolve('schema/index.json')) ajv.addSchema(schema);
.then((schema) => { return schema;
ajv.addSchema(schema); });
return schema;
});
module.exports = apiValidator; module.exports = apiValidator;

View file

@ -1,17 +1,15 @@
const _ = require('lodash'); const _ = require('lodash');
const error = require('../error'); const error = require('../error');
const definitions = require('../../schema/definitions.json'); const definitions = require('../../schema/definitions.json');
RegExp.prototype.toJSON = RegExp.prototype.toString; RegExp.prototype.toJSON = RegExp.prototype.toString;
const ajv = require('ajv')({ const ajv = require('ajv')({
verbose: true, verbose: true,
allErrors: true, allErrors: true,
format: 'full', // strict regexes for format checks format: 'full', // strict regexes for format checks
coerceTypes: true, coerceTypes: true,
schemas: [ schemas: [definitions],
definitions
]
}); });
/** /**
@ -20,30 +18,26 @@ const ajv = require('ajv')({
* @param {Object} payload * @param {Object} payload
* @returns {Promise} * @returns {Promise}
*/ */
function validator (schema, payload) { function validator(schema, payload) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
if (!payload) { if (!payload) {
reject(new error.InternalValidationError('Payload is falsy')); reject(new error.InternalValidationError('Payload is falsy'));
} else { } else {
try { try {
let validate = ajv.compile(schema); const validate = ajv.compile(schema);
let valid = validate(payload); const valid = validate(payload);
if (valid && !validate.errors) { if (valid && !validate.errors) {
resolve(_.cloneDeep(payload)); resolve(_.cloneDeep(payload));
} else { } else {
let message = ajv.errorsText(validate.errors); const message = ajv.errorsText(validate.errors);
reject(new error.InternalValidationError(message)); reject(new error.InternalValidationError(message));
} }
} catch (err) { } catch (err) {
reject(err); reject(err);
} }
} }
}); });
} }
module.exports = validator; module.exports = validator;

View file

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

View file

@ -1,15 +1,14 @@
const db = require('./db'); const db = require('./db');
const logger = require('./logger').migrate; const logger = require('./logger').migrate;
module.exports = { module.exports = {
latest: function () { latest: function () {
return db.migrate.currentVersion() return db.migrate.currentVersion().then((version) => {
.then((version) => { logger.info('Current database version:', version);
logger.info('Current database version:', version); return db.migrate.latest({
return db.migrate.latest({ tableName: 'migrations',
tableName: 'migrations', directory: 'migrations',
directory: 'migrations'
});
}); });
} });
},
}; };

View file

@ -1,5 +1,5 @@
const migrate_name = 'initial-schema'; const migrate_name = 'initial-schema';
const logger = require('../logger').migrate; const logger = require('../logger').migrate;
/** /**
* Migrate * Migrate
@ -10,19 +10,20 @@ const logger = require('../logger').migrate;
* @param {Promise} Promise * @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
exports.up = function (knex/*, Promise*/) { exports.up = function (knex /*, Promise */) {
logger.info('[' + migrate_name + '] Migrating Up...'); logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema.createTable('auth', (table) => { return knex.schema
table.increments().primary(); .createTable('auth', (table) => {
table.dateTime('created_on').notNull(); table.increments().primary();
table.dateTime('modified_on').notNull(); table.dateTime('created_on').notNull();
table.integer('user_id').notNull().unsigned(); table.dateTime('modified_on').notNull();
table.string('type', 30).notNull(); table.integer('user_id').notNull().unsigned();
table.string('secret').notNull(); table.string('type', 30).notNull();
table.json('meta').notNull(); table.string('secret').notNull();
table.integer('is_deleted').notNull().unsigned().defaultTo(0); table.json('meta').notNull();
}) table.integer('is_deleted').notNull().unsigned().defaultTo(0);
})
.then(() => { .then(() => {
logger.info('[' + migrate_name + '] auth Table created'); logger.info('[' + migrate_name + '] auth Table created');
@ -189,7 +190,6 @@ exports.up = function (knex/*, Promise*/) {
.then(() => { .then(() => {
logger.info('[' + migrate_name + '] audit_log Table created'); logger.info('[' + migrate_name + '] audit_log Table created');
}); });
}; };
/** /**
@ -200,6 +200,6 @@ exports.up = function (knex/*, Promise*/) {
* @returns {Promise} * @returns {Promise}
*/ */
exports.down = function (knex, Promise) { exports.down = function (knex, Promise) {
logger.warn('[' + migrate_name + '] 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);
}; };

View file

@ -1,5 +1,5 @@
const migrate_name = 'websockets'; const migrate_name = 'websockets';
const logger = require('../logger').migrate; const logger = require('../logger').migrate;
/** /**
* Migrate * Migrate
@ -10,16 +10,16 @@ const logger = require('../logger').migrate;
* @param {Promise} Promise * @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
exports.up = function (knex/*, Promise*/) { exports.up = function (knex /*, Promise */) {
logger.info('[' + migrate_name + '] Migrating Up...'); logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema.table('proxy_host', function (proxy_host) { return knex.schema
proxy_host.integer('allow_websocket_upgrade').notNull().unsigned().defaultTo(0); .table('proxy_host', function (proxy_host) {
}) proxy_host.integer('allow_websocket_upgrade').notNull().unsigned().defaultTo(0);
})
.then(() => { .then(() => {
logger.info('[' + migrate_name + '] proxy_host Table altered'); logger.info('[' + migrate_name + '] proxy_host Table altered');
}); });
}; };
/** /**
@ -30,6 +30,6 @@ exports.up = function (knex/*, Promise*/) {
* @returns {Promise} * @returns {Promise}
*/ */
exports.down = function (knex, Promise) { exports.down = function (knex, Promise) {
logger.warn('[' + migrate_name + '] 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);
}; };

View file

@ -1,5 +1,5 @@
const migrate_name = 'forward_host'; const migrate_name = 'forward_host';
const logger = require('../logger').migrate; const logger = require('../logger').migrate;
/** /**
* Migrate * Migrate
@ -10,12 +10,13 @@ const logger = require('../logger').migrate;
* @param {Promise} Promise * @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
exports.up = function (knex/*, Promise*/) { exports.up = function (knex /*, Promise */) {
logger.info('[' + migrate_name + '] Migrating Up...'); logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema.table('proxy_host', function (proxy_host) { return knex.schema
proxy_host.renameColumn('forward_ip', 'forward_host'); .table('proxy_host', function (proxy_host) {
}) proxy_host.renameColumn('forward_ip', 'forward_host');
})
.then(() => { .then(() => {
logger.info('[' + migrate_name + '] proxy_host Table altered'); logger.info('[' + migrate_name + '] proxy_host Table altered');
}); });
@ -29,6 +30,6 @@ exports.up = function (knex/*, Promise*/) {
* @returns {Promise} * @returns {Promise}
*/ */
exports.down = function (knex, Promise) { exports.down = function (knex, Promise) {
logger.warn('[' + migrate_name + '] 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);
}; };

View file

@ -1,5 +1,5 @@
const migrate_name = 'http2_support'; const migrate_name = 'http2_support';
const logger = require('../logger').migrate; const logger = require('../logger').migrate;
/** /**
* Migrate * Migrate
@ -10,12 +10,13 @@ const logger = require('../logger').migrate;
* @param {Promise} Promise * @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
exports.up = function (knex/*, Promise*/) { exports.up = function (knex /*, Promise */) {
logger.info('[' + migrate_name + '] Migrating Up...'); logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema.table('proxy_host', function (proxy_host) { return knex.schema
proxy_host.integer('http2_support').notNull().unsigned().defaultTo(0); .table('proxy_host', function (proxy_host) {
}) proxy_host.integer('http2_support').notNull().unsigned().defaultTo(0);
})
.then(() => { .then(() => {
logger.info('[' + migrate_name + '] proxy_host Table altered'); logger.info('[' + migrate_name + '] proxy_host Table altered');
@ -43,7 +44,6 @@ exports.up = function (knex/*, Promise*/) {
* @returns {Promise} * @returns {Promise}
*/ */
exports.down = function (knex, Promise) { exports.down = function (knex, Promise) {
logger.warn('[' + migrate_name + '] 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);
}; };

View file

@ -1,5 +1,5 @@
const migrate_name = 'forward_scheme'; const migrate_name = 'forward_scheme';
const logger = require('../logger').migrate; const logger = require('../logger').migrate;
/** /**
* Migrate * Migrate
@ -10,12 +10,13 @@ const logger = require('../logger').migrate;
* @param {Promise} Promise * @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
exports.up = function (knex/*, Promise*/) { exports.up = function (knex /*, Promise */) {
logger.info('[' + migrate_name + '] Migrating Up...'); logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema.table('proxy_host', function (proxy_host) { return knex.schema
proxy_host.string('forward_scheme').notNull().defaultTo('http'); .table('proxy_host', function (proxy_host) {
}) proxy_host.string('forward_scheme').notNull().defaultTo('http');
})
.then(() => { .then(() => {
logger.info('[' + migrate_name + '] proxy_host Table altered'); logger.info('[' + migrate_name + '] proxy_host Table altered');
}); });
@ -29,6 +30,6 @@ exports.up = function (knex/*, Promise*/) {
* @returns {Promise} * @returns {Promise}
*/ */
exports.down = function (knex, Promise) { exports.down = function (knex, Promise) {
logger.warn('[' + migrate_name + '] 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);
}; };

View file

@ -1,5 +1,5 @@
const migrate_name = 'disabled'; const migrate_name = 'disabled';
const logger = require('../logger').migrate; const logger = require('../logger').migrate;
/** /**
* Migrate * Migrate
@ -10,12 +10,13 @@ const logger = require('../logger').migrate;
* @param {Promise} Promise * @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
exports.up = function (knex/*, Promise*/) { exports.up = function (knex /*, Promise */) {
logger.info('[' + migrate_name + '] Migrating Up...'); logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema.table('proxy_host', function (proxy_host) { return knex.schema
proxy_host.integer('enabled').notNull().unsigned().defaultTo(1); .table('proxy_host', function (proxy_host) {
}) proxy_host.integer('enabled').notNull().unsigned().defaultTo(1);
})
.then(() => { .then(() => {
logger.info('[' + migrate_name + '] proxy_host Table altered'); logger.info('[' + migrate_name + '] proxy_host Table altered');
@ -50,6 +51,6 @@ exports.up = function (knex/*, Promise*/) {
* @returns {Promise} * @returns {Promise}
*/ */
exports.down = function (knex, Promise) { exports.down = function (knex, Promise) {
logger.warn('[' + migrate_name + '] 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);
}; };

View file

@ -1,5 +1,5 @@
const migrate_name = 'custom_locations'; const migrate_name = 'custom_locations';
const logger = require('../logger').migrate; const logger = require('../logger').migrate;
/** /**
* Migrate * Migrate
@ -11,12 +11,13 @@ const logger = require('../logger').migrate;
* @param {Promise} Promise * @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
exports.up = function (knex/*, Promise*/) { exports.up = function (knex /*, Promise */) {
logger.info('[' + migrate_name + '] Migrating Up...'); logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema.table('proxy_host', function (proxy_host) { return knex.schema
proxy_host.json('locations'); .table('proxy_host', function (proxy_host) {
}) proxy_host.json('locations');
})
.then(() => { .then(() => {
logger.info('[' + migrate_name + '] proxy_host Table altered'); logger.info('[' + migrate_name + '] proxy_host Table altered');
}); });
@ -30,6 +31,6 @@ exports.up = function (knex/*, Promise*/) {
* @returns {Promise} * @returns {Promise}
*/ */
exports.down = function (knex, Promise) { exports.down = function (knex, Promise) {
logger.warn('[' + migrate_name + '] 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);
}; };

View file

@ -1,5 +1,5 @@
const migrate_name = 'hsts'; const migrate_name = 'hsts';
const logger = require('../logger').migrate; const logger = require('../logger').migrate;
/** /**
* Migrate * Migrate
@ -10,13 +10,14 @@ const logger = require('../logger').migrate;
* @param {Promise} Promise * @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
exports.up = function (knex/*, Promise*/) { exports.up = function (knex /*, Promise */) {
logger.info('[' + migrate_name + '] Migrating Up...'); logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema.table('proxy_host', function (proxy_host) { return knex.schema
proxy_host.integer('hsts_enabled').notNull().unsigned().defaultTo(0); .table('proxy_host', function (proxy_host) {
proxy_host.integer('hsts_subdomains').notNull().unsigned().defaultTo(0); proxy_host.integer('hsts_enabled').notNull().unsigned().defaultTo(0);
}) proxy_host.integer('hsts_subdomains').notNull().unsigned().defaultTo(0);
})
.then(() => { .then(() => {
logger.info('[' + migrate_name + '] proxy_host Table altered'); logger.info('[' + migrate_name + '] proxy_host Table altered');
@ -46,6 +47,6 @@ exports.up = function (knex/*, Promise*/) {
* @returns {Promise} * @returns {Promise}
*/ */
exports.down = function (knex, Promise) { exports.down = function (knex, Promise) {
logger.warn('[' + migrate_name + '] 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);
}; };

View file

@ -1,5 +1,5 @@
const migrate_name = 'settings'; const migrate_name = 'settings';
const logger = require('../logger').migrate; const logger = require('../logger').migrate;
/** /**
* Migrate * Migrate
@ -10,16 +10,17 @@ const logger = require('../logger').migrate;
* @param {Promise} Promise * @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
exports.up = function (knex/*, Promise*/) { exports.up = function (knex /*, Promise */) {
logger.info('[' + migrate_name + '] Migrating Up...'); logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema.createTable('setting', (table) => { return knex.schema
table.string('id').notNull().primary(); .createTable('setting', (table) => {
table.string('name', 100).notNull(); table.string('id').notNull().primary();
table.string('description', 255).notNull(); table.string('name', 100).notNull();
table.string('value', 255).notNull(); table.string('description', 255).notNull();
table.json('meta').notNull(); table.string('value', 255).notNull();
}) table.json('meta').notNull();
})
.then(() => { .then(() => {
logger.info('[' + migrate_name + '] setting Table created'); logger.info('[' + migrate_name + '] setting Table created');
}); });
@ -33,6 +34,6 @@ exports.up = function (knex/*, Promise*/) {
* @returns {Promise} * @returns {Promise}
*/ */
exports.down = function (knex, Promise) { exports.down = function (knex, Promise) {
logger.warn('[' + migrate_name + '] 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);
}; };

View file

@ -1,5 +1,5 @@
const migrate_name = 'access_list_client'; const migrate_name = 'access_list_client';
const logger = require('../logger').migrate; const logger = require('../logger').migrate;
/** /**
* Migrate * Migrate
@ -10,20 +10,19 @@ const logger = require('../logger').migrate;
* @param {Promise} Promise * @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
exports.up = function (knex/*, Promise*/) { exports.up = function (knex /*, Promise */) {
logger.info('[' + migrate_name + '] Migrating Up...'); logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema.createTable('access_list_client', (table) => { return knex.schema
table.increments().primary(); .createTable('access_list_client', (table) => {
table.dateTime('created_on').notNull(); table.increments().primary();
table.dateTime('modified_on').notNull(); table.dateTime('created_on').notNull();
table.integer('access_list_id').notNull().unsigned(); table.dateTime('modified_on').notNull();
table.string('address').notNull(); table.integer('access_list_id').notNull().unsigned();
table.string('directive').notNull(); table.string('address').notNull();
table.json('meta').notNull(); table.string('directive').notNull();
table.json('meta').notNull();
}) })
.then(function () { .then(function () {
logger.info('[' + migrate_name + '] access_list_client Table created'); logger.info('[' + migrate_name + '] access_list_client Table created');
@ -43,11 +42,10 @@ exports.up = function (knex/*, Promise*/) {
* @param {Promise} Promise * @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
exports.down = function (knex/*, Promise*/) { exports.down = function (knex /*, Promise */) {
logger.info('[' + migrate_name + '] Migrating Down...'); logger.info('[' + migrate_name + '] Migrating Down...');
return knex.schema.dropTable('access_list_client') return knex.schema.dropTable('access_list_client').then(() => {
.then(() => { logger.info('[' + migrate_name + '] access_list_client Table dropped');
logger.info('[' + migrate_name + '] access_list_client Table dropped'); });
});
}; };

View file

@ -1,5 +1,5 @@
const migrate_name = 'access_list_client_fix'; const migrate_name = 'access_list_client_fix';
const logger = require('../logger').migrate; const logger = require('../logger').migrate;
/** /**
* Migrate * Migrate
@ -10,12 +10,13 @@ const logger = require('../logger').migrate;
* @param {Promise} Promise * @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
exports.up = function (knex/*, Promise*/) { exports.up = function (knex /*, Promise */) {
logger.info('[' + migrate_name + '] Migrating Up...'); logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema.table('access_list', function (access_list) { return knex.schema
access_list.renameColumn('satify_any', 'satisfy_any'); .table('access_list', function (access_list) {
}) access_list.renameColumn('satify_any', 'satisfy_any');
})
.then(() => { .then(() => {
logger.info('[' + migrate_name + '] access_list Table altered'); logger.info('[' + migrate_name + '] access_list Table altered');
}); });
@ -29,6 +30,6 @@ exports.up = function (knex/*, Promise*/) {
* @returns {Promise} * @returns {Promise}
*/ */
exports.down = function (knex, Promise) { exports.down = function (knex, Promise) {
logger.warn('[' + migrate_name + '] 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);
}; };

View file

@ -1,5 +1,5 @@
const migrate_name = 'pass_auth'; const migrate_name = 'pass_auth';
const logger = require('../logger').migrate; const logger = require('../logger').migrate;
/** /**
* Migrate * Migrate
@ -10,13 +10,13 @@ const logger = require('../logger').migrate;
* @param {Promise} Promise * @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
exports.up = function (knex/*, Promise*/) { exports.up = function (knex /*, Promise */) {
logger.info('[' + migrate_name + '] Migrating Up...'); logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema.table('access_list', function (access_list) { return knex.schema
access_list.integer('pass_auth').notNull().defaultTo(1); .table('access_list', function (access_list) {
}) access_list.integer('pass_auth').notNull().defaultTo(1);
})
.then(() => { .then(() => {
logger.info('[' + migrate_name + '] access_list Table altered'); logger.info('[' + migrate_name + '] access_list Table altered');
}); });
@ -29,12 +29,13 @@ exports.up = function (knex/*, Promise*/) {
* @param {Promise} Promise * @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
exports.down = function (knex/*, Promise*/) { exports.down = function (knex /*, Promise */) {
logger.info('[' + migrate_name + '] Migrating Down...'); logger.info('[' + migrate_name + '] Migrating Down...');
return knex.schema.table('access_list', function (access_list) { return knex.schema
access_list.dropColumn('pass_auth'); .table('access_list', function (access_list) {
}) access_list.dropColumn('pass_auth');
})
.then(() => { .then(() => {
logger.info('[' + migrate_name + '] access_list pass_auth Column dropped'); logger.info('[' + migrate_name + '] access_list pass_auth Column dropped');
}); });

View file

@ -1,5 +1,5 @@
const migrate_name = 'redirection_scheme'; const migrate_name = 'redirection_scheme';
const logger = require('../logger').migrate; const logger = require('../logger').migrate;
/** /**
* Migrate * Migrate
@ -10,13 +10,13 @@ const logger = require('../logger').migrate;
* @param {Promise} Promise * @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
exports.up = function (knex/*, Promise*/) { exports.up = function (knex /*, Promise */) {
logger.info('[' + migrate_name + '] Migrating Up...'); logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema.table('redirection_host', (table) => { return knex.schema
table.string('forward_scheme').notNull().defaultTo('$scheme'); .table('redirection_host', (table) => {
}) table.string('forward_scheme').notNull().defaultTo('$scheme');
})
.then(function () { .then(function () {
logger.info('[' + migrate_name + '] redirection_host Table altered'); logger.info('[' + migrate_name + '] redirection_host Table altered');
}); });
@ -29,12 +29,13 @@ exports.up = function (knex/*, Promise*/) {
* @param {Promise} Promise * @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
exports.down = function (knex/*, Promise*/) { exports.down = function (knex /*, Promise */) {
logger.info('[' + migrate_name + '] Migrating Down...'); logger.info('[' + migrate_name + '] Migrating Down...');
return knex.schema.table('redirection_host', (table) => { return knex.schema
table.dropColumn('forward_scheme'); .table('redirection_host', (table) => {
}) table.dropColumn('forward_scheme');
})
.then(function () { .then(function () {
logger.info('[' + migrate_name + '] redirection_host Table altered'); logger.info('[' + migrate_name + '] redirection_host Table altered');
}); });

View file

@ -1,5 +1,5 @@
const migrate_name = 'redirection_status_code'; const migrate_name = 'redirection_status_code';
const logger = require('../logger').migrate; const logger = require('../logger').migrate;
/** /**
* Migrate * Migrate
@ -10,13 +10,13 @@ const logger = require('../logger').migrate;
* @param {Promise} Promise * @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
exports.up = function (knex/*, Promise*/) { exports.up = function (knex /*, Promise */) {
logger.info('[' + migrate_name + '] Migrating Up...'); logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema.table('redirection_host', (table) => { return knex.schema
table.integer('forward_http_code').notNull().unsigned().defaultTo(302); .table('redirection_host', (table) => {
}) table.integer('forward_http_code').notNull().unsigned().defaultTo(302);
})
.then(function () { .then(function () {
logger.info('[' + migrate_name + '] redirection_host Table altered'); logger.info('[' + migrate_name + '] redirection_host Table altered');
}); });
@ -29,12 +29,13 @@ exports.up = function (knex/*, Promise*/) {
* @param {Promise} Promise * @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
exports.down = function (knex/*, Promise*/) { exports.down = function (knex /*, Promise */) {
logger.info('[' + migrate_name + '] Migrating Down...'); logger.info('[' + migrate_name + '] Migrating Down...');
return knex.schema.table('redirection_host', (table) => { return knex.schema
table.dropColumn('forward_http_code'); .table('redirection_host', (table) => {
}) table.dropColumn('forward_http_code');
})
.then(function () { .then(function () {
logger.info('[' + migrate_name + '] redirection_host Table altered'); logger.info('[' + migrate_name + '] redirection_host Table altered');
}); });

View file

@ -1,39 +1,41 @@
const migrate_name = 'stream_domain'; const migrate_name = 'stream_domain';
const logger = require('../logger').migrate; const logger = require('../logger').migrate;
/** /**
* Migrate * Migrate
* *
* @see http://knexjs.org/#Schema * @see http://knexjs.org/#Schema
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise * @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
exports.up = function (knex/*, Promise*/) { exports.up = function (knex /*, Promise */) {
logger.info('[' + migrate_name + '] Migrating Up...'); logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema.table('stream', (table) => { return knex.schema
table.renameColumn('forward_ip', 'forwarding_host'); .table('stream', (table) => {
}) table.renameColumn('forward_ip', 'forwarding_host');
})
.then(function () { .then(function () {
logger.info('[' + migrate_name + '] stream Table altered'); logger.info('[' + migrate_name + '] stream Table altered');
}); });
}; };
/** /**
* Undo Migrate * Undo Migrate
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise * @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
exports.down = function (knex/*, Promise*/) { exports.down = function (knex /*, Promise */) {
logger.info('[' + migrate_name + '] Migrating Down...'); logger.info('[' + migrate_name + '] Migrating Down...');
return knex.schema.table('stream', (table) => { return knex.schema
table.renameColumn('forwarding_host', 'forward_ip'); .table('stream', (table) => {
}) table.renameColumn('forwarding_host', 'forward_ip');
})
.then(function () { .then(function () {
logger.info('[' + migrate_name + '] stream Table altered'); logger.info('[' + migrate_name + '] stream Table altered');
}); });

View file

@ -1,5 +1,5 @@
const migrate_name = 'stream_domain'; const migrate_name = 'stream_domain';
const logger = require('../logger').migrate; const logger = require('../logger').migrate;
const internalNginx = require('../internal/nginx'); const internalNginx = require('../internal/nginx');
async function regenerateDefaultHost(knex) { async function regenerateDefaultHost(knex) {
@ -9,7 +9,8 @@ async function regenerateDefaultHost(knex) {
return Promise.resolve(); return Promise.resolve();
} }
return internalNginx.deleteConfig('default') return internalNginx
.deleteConfig('default')
.then(() => { .then(() => {
return internalNginx.generateConfig('default', row); return internalNginx.generateConfig('default', row);
}) })
@ -22,14 +23,14 @@ async function regenerateDefaultHost(knex) {
} }
/** /**
* Migrate * Migrate
* *
* @see http://knexjs.org/#Schema * @see http://knexjs.org/#Schema
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise * @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
exports.up = function (knex) { exports.up = function (knex) {
logger.info('[' + migrate_name + '] Migrating Up...'); logger.info('[' + migrate_name + '] Migrating Up...');
@ -37,14 +38,14 @@ exports.up = function (knex) {
}; };
/** /**
* Undo Migrate * Undo Migrate
* *
* @param {Object} knex * @param {Object} knex
* @param {Promise} Promise * @param {Promise} Promise
* @returns {Promise} * @returns {Promise}
*/ */
exports.down = function (knex) { exports.down = function (knex) {
logger.info('[' + migrate_name + '] Migrating Down...'); logger.info('[' + migrate_name + '] Migrating Down...');
return regenerateDefaultHost(knex); return regenerateDefaultHost(knex);
}; };

View file

@ -1,18 +1,18 @@
// Objection Docs: // Objection Docs:
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
const db = require('../db'); const db = require('../db');
const Model = require('objection').Model; const Model = require('objection').Model;
const User = require('./user'); const User = require('./user');
const AccessListAuth = require('./access_list_auth'); const AccessListAuth = require('./access_list_auth');
const AccessListClient = require('./access_list_client'); const AccessListClient = require('./access_list_client');
const now = require('./now_helper'); const now = require('./now_helper');
Model.knex(db); Model.knex(db);
class AccessList extends Model { class AccessList extends Model {
$beforeInsert () { $beforeInsert() {
this.created_on = now(); this.created_on = now();
this.modified_on = now(); this.modified_on = now();
// Default for meta // Default for meta
@ -21,64 +21,64 @@ class AccessList extends Model {
} }
} }
$beforeUpdate () { $beforeUpdate() {
this.modified_on = now(); this.modified_on = now();
} }
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'); 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: function (qb) { modify: function (qb) {
qb.where('user.is_deleted', 0); 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: ProxyHost, 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: function (qb) { modify: function (qb) {
qb.where('proxy_host.is_deleted', 0); qb.where('proxy_host.is_deleted', 0);
} },
} },
}; };
} }
} }

View file

@ -1,15 +1,15 @@
// Objection Docs: // Objection Docs:
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
const db = require('../db'); const db = require('../db');
const Model = require('objection').Model; const Model = require('objection').Model;
const now = require('./now_helper'); const now = require('./now_helper');
Model.knex(db); Model.knex(db);
class AccessListAuth extends Model { class AccessListAuth extends Model {
$beforeInsert () { $beforeInsert() {
this.created_on = now(); this.created_on = now();
this.modified_on = now(); this.modified_on = now();
// Default for meta // Default for meta
@ -18,35 +18,35 @@ class AccessListAuth extends Model {
} }
} }
$beforeUpdate () { $beforeUpdate() {
this.modified_on = now(); this.modified_on = now();
} }
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: require('./access_list'), 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: function (qb) { modify: function (qb) {
qb.where('access_list.is_deleted', 0); qb.where('access_list.is_deleted', 0);
} },
} },
}; };
} }
} }

View file

@ -1,15 +1,15 @@
// Objection Docs: // Objection Docs:
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
const db = require('../db'); const db = require('../db');
const Model = require('objection').Model; const Model = require('objection').Model;
const now = require('./now_helper'); const now = require('./now_helper');
Model.knex(db); Model.knex(db);
class AccessListClient extends Model { class AccessListClient extends Model {
$beforeInsert () { $beforeInsert() {
this.created_on = now(); this.created_on = now();
this.modified_on = now(); this.modified_on = now();
// Default for meta // Default for meta
@ -18,35 +18,35 @@ class AccessListClient extends Model {
} }
} }
$beforeUpdate () { $beforeUpdate() {
this.modified_on = now(); this.modified_on = now();
} }
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: require('./access_list'), 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: function (qb) { modify: function (qb) {
qb.where('access_list.is_deleted', 0); qb.where('access_list.is_deleted', 0);
} },
} },
}; };
} }
} }

View file

@ -1,16 +1,16 @@
// Objection Docs: // Objection Docs:
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
const db = require('../db'); const db = require('../db');
const Model = require('objection').Model; const Model = require('objection').Model;
const User = require('./user'); const User = require('./user');
const now = require('./now_helper'); const now = require('./now_helper');
Model.knex(db); Model.knex(db);
class AuditLog extends Model { class AuditLog extends Model {
$beforeInsert () { $beforeInsert() {
this.created_on = now(); this.created_on = now();
this.modified_on = now(); this.modified_on = now();
// Default for meta // Default for meta
@ -19,32 +19,32 @@ class AuditLog extends Model {
} }
} }
$beforeUpdate () { $beforeUpdate() {
this.modified_on = now(); this.modified_on = now();
} }
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() {
return { return {
user: { user: {
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',
} },
} },
}; };
} }
} }

View file

@ -2,30 +2,29 @@
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
const bcrypt = require('bcrypt'); const bcrypt = require('bcrypt');
const db = require('../db'); const db = require('../db');
const Model = require('objection').Model; const Model = require('objection').Model;
const User = require('./user'); const User = require('./user');
const now = require('./now_helper'); const now = require('./now_helper');
Model.knex(db); Model.knex(db);
function encryptPassword () { function encryptPassword() {
/* jshint -W040 */ /* jshint -W040 */
let _this = this; const _this = this;
if (_this.type === 'password' && _this.secret) { if (_this.type === 'password' && _this.secret) {
return bcrypt.hash(_this.secret, 13) return bcrypt.hash(_this.secret, 13).then(function (hash) {
.then(function (hash) { _this.secret = hash;
_this.secret = hash; });
});
} }
return null; return null;
} }
class Auth extends Model { class Auth extends Model {
$beforeInsert (queryContext) { $beforeInsert(queryContext) {
this.created_on = now(); this.created_on = now();
this.modified_on = now(); this.modified_on = now();
// Default for meta // Default for meta
@ -36,7 +35,7 @@ class Auth extends Model {
return encryptPassword.apply(this, queryContext); return encryptPassword.apply(this, queryContext);
} }
$beforeUpdate (queryContext) { $beforeUpdate(queryContext) {
this.modified_on = now(); this.modified_on = now();
return encryptPassword.apply(this, queryContext); return encryptPassword.apply(this, queryContext);
} }
@ -47,35 +46,35 @@ class Auth extends Model {
* @param {String} password * @param {String} password
* @returns {Promise} * @returns {Promise}
*/ */
verifyPassword (password) { verifyPassword(password) {
return bcrypt.compare(password, this.secret); return bcrypt.compare(password, this.secret);
} }
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() {
return { return {
user: { user: {
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,
} },
} },
}; };
} }
} }

View file

@ -1,16 +1,16 @@
// Objection Docs: // Objection Docs:
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
const db = require('../db'); const db = require('../db');
const Model = require('objection').Model; const Model = require('objection').Model;
const User = require('./user'); const User = require('./user');
const now = require('./now_helper'); const now = require('./now_helper');
Model.knex(db); Model.knex(db);
class Certificate extends Model { class Certificate extends Model {
$beforeInsert () { $beforeInsert() {
this.created_on = now(); this.created_on = now();
this.modified_on = now(); this.modified_on = now();
// Default for expires_on // Default for expires_on
@ -29,35 +29,35 @@ class Certificate extends Model {
} }
} }
$beforeUpdate () { $beforeUpdate() {
this.modified_on = now(); this.modified_on = now();
} }
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() {
return { return {
owner: { owner: {
relation: Model.HasOneRelation, relation: Model.HasOneRelation,
modelClass: User, modelClass: User,
join: { join: {
from: 'certificate.owner_user_id', from: 'certificate.owner_user_id',
to: 'user.id' to: 'user.id',
}, },
modify: function (qb) { modify: function (qb) {
qb.where('user.is_deleted', 0); qb.where('user.is_deleted', 0);
} },
} },
}; };
} }
} }

View file

@ -1,17 +1,17 @@
// Objection Docs: // Objection Docs:
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
const db = require('../db'); const db = require('../db');
const Model = require('objection').Model; const Model = require('objection').Model;
const User = require('./user'); const User = require('./user');
const Certificate = require('./certificate'); const Certificate = require('./certificate');
const now = require('./now_helper'); const now = require('./now_helper');
Model.knex(db); Model.knex(db);
class DeadHost extends Model { class DeadHost extends Model {
$beforeInsert () { $beforeInsert() {
this.created_on = now(); this.created_on = now();
this.modified_on = now(); this.modified_on = now();
// Default for domain_names // Default for domain_names
@ -25,46 +25,46 @@ class DeadHost extends Model {
} }
} }
$beforeUpdate () { $beforeUpdate() {
this.modified_on = now(); this.modified_on = now();
} }
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() {
return { return {
owner: { owner: {
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: function (qb) { modify: function (qb) {
qb.where('user.is_deleted', 0); 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: function (qb) { modify: function (qb) {
qb.where('certificate.is_deleted', 0); qb.where('certificate.is_deleted', 0);
} },
} },
}; };
} }
} }

View file

@ -1,12 +1,11 @@
const db = require('../db'); const db = require('../db');
const config = require('../lib/config'); const config = require('../lib/config');
const Model = require('objection').Model; const Model = require('objection').Model;
Model.knex(db); Model.knex(db);
module.exports = function () { module.exports = function () {
if (config.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,18 +1,18 @@
// Objection Docs: // Objection Docs:
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
const db = require('../db'); const db = require('../db');
const Model = require('objection').Model; const Model = require('objection').Model;
const User = require('./user'); const User = require('./user');
const AccessList = require('./access_list'); const AccessList = require('./access_list');
const Certificate = require('./certificate'); const Certificate = require('./certificate');
const now = require('./now_helper'); const now = require('./now_helper');
Model.knex(db); Model.knex(db);
class ProxyHost extends Model { class ProxyHost extends Model {
$beforeInsert () { $beforeInsert() {
this.created_on = now(); this.created_on = now();
this.modified_on = now(); this.modified_on = now();
// Default for domain_names // Default for domain_names
@ -26,57 +26,57 @@ class ProxyHost extends Model {
} }
} }
$beforeUpdate () { $beforeUpdate() {
this.modified_on = now(); this.modified_on = now();
} }
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() {
return { return {
owner: { owner: {
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: function (qb) { modify: function (qb) {
qb.where('user.is_deleted', 0); 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: function (qb) { modify: function (qb) {
qb.where('access_list.is_deleted', 0); 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: function (qb) { modify: function (qb) {
qb.where('certificate.is_deleted', 0); qb.where('certificate.is_deleted', 0);
} },
} },
}; };
} }
} }

View file

@ -1,18 +1,17 @@
// Objection Docs: // Objection Docs:
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
const db = require('../db'); const db = require('../db');
const Model = require('objection').Model; const Model = require('objection').Model;
const User = require('./user'); const User = require('./user');
const Certificate = require('./certificate'); const Certificate = require('./certificate');
const now = require('./now_helper'); const now = require('./now_helper');
Model.knex(db); Model.knex(db);
class RedirectionHost extends Model { class RedirectionHost extends Model {
$beforeInsert () { $beforeInsert() {
this.created_on = now(); this.created_on = now();
this.modified_on = now(); this.modified_on = now();
// Default for domain_names // Default for domain_names
@ -28,46 +27,46 @@ class RedirectionHost extends Model {
this.domain_names.sort(); this.domain_names.sort();
} }
$beforeUpdate () { $beforeUpdate() {
this.modified_on = now(); this.modified_on = now();
} }
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() {
return { return {
owner: { owner: {
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: function (qb) { modify: function (qb) {
qb.where('user.is_deleted', 0); 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: function (qb) { modify: function (qb) {
qb.where('certificate.is_deleted', 0); qb.where('certificate.is_deleted', 0);
} },
} },
}; };
} }
} }

View file

@ -1,28 +1,28 @@
// Objection Docs: // Objection Docs:
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
const db = require('../db'); const db = require('../db');
const Model = require('objection').Model; const Model = require('objection').Model;
Model.knex(db); Model.knex(db);
class Setting extends Model { class Setting extends Model {
$beforeInsert () { $beforeInsert() {
// Default for meta // Default for meta
if (typeof this.meta === 'undefined') { if (typeof this.meta === 'undefined') {
this.meta = {}; this.meta = {};
} }
} }
static get name () { static get name() {
return 'Setting'; return 'Setting';
} }
static get tableName () { static get tableName() {
return 'setting'; return 'setting';
} }
static get jsonAttributes () { static get jsonAttributes() {
return ['meta']; return ['meta'];
} }
} }

View file

@ -1,16 +1,16 @@
// Objection Docs: // Objection Docs:
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
const db = require('../db'); const db = require('../db');
const Model = require('objection').Model; const Model = require('objection').Model;
const User = require('./user'); const User = require('./user');
const now = require('./now_helper'); const now = require('./now_helper');
Model.knex(db); Model.knex(db);
class Stream extends Model { class Stream extends Model {
$beforeInsert () { $beforeInsert() {
this.created_on = now(); this.created_on = now();
this.modified_on = now(); this.modified_on = now();
// Default for meta // Default for meta
@ -19,35 +19,35 @@ class Stream extends Model {
} }
} }
$beforeUpdate () { $beforeUpdate() {
this.modified_on = now(); this.modified_on = now();
} }
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() {
return { return {
owner: { owner: {
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: function (qb) { modify: function (qb) {
qb.where('user.is_deleted', 0); qb.where('user.is_deleted', 0);
} },
} },
}; };
} }
} }

View file

@ -3,16 +3,15 @@
and then has abilities after that. and then has abilities after that.
*/ */
const _ = require('lodash'); const _ = require('lodash');
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
const crypto = require('crypto'); const crypto = require('crypto');
const config = require('../lib/config'); const config = require('../lib/config');
const error = require('../lib/error'); const error = require('../lib/error');
const logger = require('../logger').global; const logger = require('../logger').global;
const ALGO = 'RS256'; const ALGO = 'RS256';
module.exports = function () { module.exports = function () {
let token_data = {}; let token_data = {};
const self = { const self = {
@ -27,12 +26,10 @@ module.exports = function () {
// 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) payload.jti = crypto.randomBytes(12).toString('base64').substring(-8);
.toString('base64')
.substring(-8);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
jwt.sign(payload, config.getPrivateKey(), options, (err, token) => { jwt.sign(payload, config.getPrivateKey(), options, (err, token) => {
@ -41,8 +38,8 @@ module.exports = function () {
} else { } else {
token_data = payload; token_data = payload;
resolve({ resolve({
token: token, token,
payload: payload payload,
}); });
} }
}); });
@ -62,15 +59,13 @@ module.exports = function () {
if (!token || token === null || token === 'null') { if (!token || token === null || token === 'null') {
reject(new error.AuthError('Empty token')); reject(new error.AuthError('Empty token'));
} else { } else {
jwt.verify(token, config.getPublicKey(), {ignoreExpiration: false, algorithms: [ALGO]}, (err, result) => { jwt.verify(token, config.getPublicKey(), { ignoreExpiration: false, algorithms: [ALGO] }, (err, result) => {
if (err) { if (err) {
if (err.name === 'TokenExpiredError') { if (err.name === 'TokenExpiredError') {
reject(new error.AuthError('Token has expired', err)); reject(new error.AuthError('Token has expired', err));
} else { } else {
reject(err); reject(err);
} }
} else { } else {
token_data = result; token_data = result;
resolve(token_data); resolve(token_data);
@ -81,7 +76,6 @@ module.exports = function () {
reject(err); reject(err);
} }
}); });
}, },
/** /**
@ -125,7 +119,7 @@ module.exports = function () {
} }
return default_value || 0; return default_value || 0;
} },
}; };
return self; return self;

View file

@ -1,16 +1,16 @@
// Objection Docs: // Objection Docs:
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
const db = require('../db'); const db = require('../db');
const Model = require('objection').Model; const Model = require('objection').Model;
const UserPermission = require('./user_permission'); const UserPermission = require('./user_permission');
const now = require('./now_helper'); const now = require('./now_helper');
Model.knex(db); Model.knex(db);
class User extends Model { class User extends Model {
$beforeInsert () { $beforeInsert() {
this.created_on = now(); this.created_on = now();
this.modified_on = now(); this.modified_on = now();
// Default for roles // Default for roles
@ -19,35 +19,34 @@ class User extends Model {
} }
} }
$beforeUpdate () { $beforeUpdate() {
this.modified_on = now(); this.modified_on = now();
} }
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() {
return { return {
permissions: { permissions: {
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',
} },
} },
}; };
} }
} }
module.exports = User; module.exports = User;

View file

@ -1,27 +1,27 @@
// Objection Docs: // Objection Docs:
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
const db = require('../db'); const db = require('../db');
const Model = require('objection').Model; const Model = require('objection').Model;
const now = require('./now_helper'); const now = require('./now_helper');
Model.knex(db); Model.knex(db);
class UserPermission extends Model { class UserPermission extends Model {
$beforeInsert () { $beforeInsert() {
this.created_on = now(); this.created_on = now();
this.modified_on = now(); this.modified_on = now();
} }
$beforeUpdate () { $beforeUpdate() {
this.modified_on = now(); this.modified_on = now();
} }
static get name () { static get name() {
return 'UserPermission'; return 'UserPermission';
} }
static get tableName () { static get tableName() {
return 'user_permission'; return 'user_permission';
} }
} }

View file

@ -4,19 +4,19 @@
"description": "A beautiful interface for creating Nginx endpoints", "description": "A beautiful interface for creating Nginx endpoints",
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {
"@apidevtools/json-schema-ref-parser": "11.5.4", "@apidevtools/json-schema-ref-parser": "11.5.5",
"ajv": "6.12.6", "ajv": "6.12.6",
"archiver": "7.0.1", "archiver": "7.0.1",
"batchflow": "0.4.0", "batchflow": "0.4.0",
"bcrypt": "5.1.1", "bcrypt": "5.1.1",
"body-parser": "1.20.2", "body-parser": "1.20.2",
"compression": "1.7.4", "compression": "1.7.4",
"express": "4.19.1", "express": "4.19.2",
"express-fileupload": "1.5.0", "express-fileupload": "1.5.0",
"gravatar": "1.8.2", "gravatar": "1.8.2",
"jsonwebtoken": "9.0.2", "jsonwebtoken": "9.0.2",
"knex": "3.1.0", "knex": "3.1.0",
"liquidjs": "10.10.2", "liquidjs": "10.11.0",
"lodash": "4.17.21", "lodash": "4.17.21",
"moment": "2.30.1", "moment": "2.30.1",
"mysql": "2.18.1", "mysql": "2.18.1",
@ -29,7 +29,11 @@
"author": "Jamie Curnow <jc@jc21.com> and ZoeyVid <zoeyvid@zvcdn.de>", "author": "Jamie Curnow <jc@jc21.com> and ZoeyVid <zoeyvid@zvcdn.de>",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"eslint": "8.57.0", "@eslint/js": "9.0.0",
"eslint-plugin-align-assignments": "1.1.2" "eslint": "9.0.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-prettier": "5.1.3",
"globals": "15.0.0",
"prettier": "3.2.5"
} }
} }

View file

@ -2,8 +2,8 @@
// based on: https://github.com/jlesage/docker-nginx-proxy-manager/blob/796734a3f9a87e0b1561b47fd418f82216359634/rootfs/opt/nginx-proxy-manager/bin/reset-password // based on: https://github.com/jlesage/docker-nginx-proxy-manager/blob/796734a3f9a87e0b1561b47fd418f82216359634/rootfs/opt/nginx-proxy-manager/bin/reset-password
const fs = require('fs'); const fs = require('fs');
const bcrypt = require('bcrypt'); const bcrypt = require('bcrypt');
const sqlite3 = require('sqlite3'); const sqlite3 = require('sqlite3');
function usage() { function usage() {
@ -53,7 +53,7 @@ if (fs.existsSync(process.env.DB_SQLITE_FILE)) {
console.log(`Password for user ${USER_EMAIL} has been reset.`); console.log(`Password for user ${USER_EMAIL} has been reset.`);
process.exit(0); process.exit(0);
} },
); );
}); });
} }

View file

@ -1,12 +1,12 @@
const express = require('express'); const express = require('express');
const validator = require('../../lib/validator'); const validator = require('../../lib/validator');
const jwtdecode = require('../../lib/express/jwt-decode'); const jwtdecode = require('../../lib/express/jwt-decode');
const internalAuditLog = require('../../internal/audit-log'); const internalAuditLog = require('../../internal/audit-log');
let router = express.Router({ const router = express.Router({
caseSensitive: true, caseSensitive: true,
strict: true, strict: true,
mergeParams: true mergeParams: true,
}); });
/** /**
@ -25,26 +25,28 @@ router
* Retrieve all logs * Retrieve all logs
*/ */
.get((req, res, next) => { .get((req, res, next) => {
validator({ validator(
additionalProperties: false, {
properties: { additionalProperties: false,
expand: { properties: {
$ref: 'definitions#/definitions/expand' expand: {
$ref: 'definitions#/definitions/expand',
},
query: {
$ref: 'definitions#/definitions/query',
},
}, },
query: { },
$ref: 'definitions#/definitions/query' {
} expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null,
} query: typeof req.query.query === 'string' ? req.query.query : null,
}, { },
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null), )
query: (typeof req.query.query === 'string' ? req.query.query : null)
})
.then((data) => { .then((data) => {
return internalAuditLog.getAll(res.locals.access, data.expand, data.query); return internalAuditLog.getAll(res.locals.access, data.expand, data.query);
}) })
.then((rows) => { .then((rows) => {
res.status(200) res.status(200).send(rows);
.send(rows);
}) })
.catch(next); .catch(next);
}); });

View file

@ -1,27 +1,27 @@
const express = require('express'); const express = require('express');
const pjson = require('../../package.json'); const pjson = require('../../package.json');
const error = require('../../lib/error'); const error = require('../../lib/error');
let router = express.Router({ const 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('/', (req, res/*, next*/) => { router.get('/', (req, res /*, next */) => {
let version = pjson.version.split('-').shift().split('.'); const version = pjson.version.split('-').shift().split('.');
res.status(200).send({ res.status(200).send({
status: 'OK', status: 'OK',
version: { version: {
major: parseInt(version.shift(), 10), major: parseInt(version.shift(), 10),
minor: parseInt(version.shift(), 10), minor: parseInt(version.shift(), 10),
revision: parseInt(version.shift(), 10) revision: parseInt(version.shift(), 10),
} },
}); });
}); });

View file

@ -1,13 +1,13 @@
const express = require('express'); const express = require('express');
const validator = require('../../../lib/validator'); const validator = require('../../../lib/validator');
const jwtdecode = require('../../../lib/express/jwt-decode'); const jwtdecode = require('../../../lib/express/jwt-decode');
const internalAccessList = require('../../../internal/access-list'); const internalAccessList = require('../../../internal/access-list');
const apiValidator = require('../../../lib/validator/api'); const apiValidator = require('../../../lib/validator/api');
let router = express.Router({ const router = express.Router({
caseSensitive: true, caseSensitive: true,
strict: true, strict: true,
mergeParams: true mergeParams: true,
}); });
/** /**
@ -26,26 +26,28 @@ router
* Retrieve all access-lists * Retrieve all access-lists
*/ */
.get((req, res, next) => { .get((req, res, next) => {
validator({ validator(
additionalProperties: false, {
properties: { additionalProperties: false,
expand: { properties: {
$ref: 'definitions#/definitions/expand' expand: {
$ref: 'definitions#/definitions/expand',
},
query: {
$ref: 'definitions#/definitions/query',
},
}, },
query: { },
$ref: 'definitions#/definitions/query' {
} expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null,
} query: typeof req.query.query === 'string' ? req.query.query : null,
}, { },
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null), )
query: (typeof req.query.query === 'string' ? req.query.query : null)
})
.then((data) => { .then((data) => {
return internalAccessList.getAll(res.locals.access, data.expand, data.query); return internalAccessList.getAll(res.locals.access, data.expand, data.query);
}) })
.then((rows) => { .then((rows) => {
res.status(200) res.status(200).send(rows);
.send(rows);
}) })
.catch(next); .catch(next);
}) })
@ -56,13 +58,12 @@ router
* Create a new access-list * Create a new access-list
*/ */
.post((req, res, next) => { .post((req, res, next) => {
apiValidator({$ref: 'endpoints/access-lists#/links/1/schema'}, req.body) apiValidator({ $ref: 'endpoints/access-lists#/links/1/schema' }, req.body)
.then((payload) => { .then((payload) => {
return internalAccessList.create(res.locals.access, payload); return internalAccessList.create(res.locals.access, payload);
}) })
.then((result) => { .then((result) => {
res.status(201) res.status(201).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}); });
@ -85,30 +86,32 @@ router
* Retrieve a specific access-list * Retrieve a specific access-list
*/ */
.get((req, res, next) => { .get((req, res, next) => {
validator({ validator(
required: ['list_id'], {
additionalProperties: false, required: ['list_id'],
properties: { additionalProperties: false,
list_id: { properties: {
$ref: 'definitions#/definitions/id' list_id: {
$ref: 'definitions#/definitions/id',
},
expand: {
$ref: 'definitions#/definitions/expand',
},
}, },
expand: { },
$ref: 'definitions#/definitions/expand' {
} list_id: req.params.list_id,
} expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null,
}, { },
list_id: req.params.list_id, )
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null)
})
.then((data) => { .then((data) => {
return internalAccessList.get(res.locals.access, { return internalAccessList.get(res.locals.access, {
id: parseInt(data.list_id, 10), id: parseInt(data.list_id, 10),
expand: data.expand expand: data.expand,
}); });
}) })
.then((row) => { .then((row) => {
res.status(200) res.status(200).send(row);
.send(row);
}) })
.catch(next); .catch(next);
}) })
@ -119,14 +122,13 @@ router
* Update and existing access-list * Update and existing access-list
*/ */
.put((req, res, next) => { .put((req, res, next) => {
apiValidator({$ref: 'endpoints/access-lists#/links/2/schema'}, req.body) apiValidator({ $ref: 'endpoints/access-lists#/links/2/schema' }, req.body)
.then((payload) => { .then((payload) => {
payload.id = parseInt(req.params.list_id, 10); payload.id = parseInt(req.params.list_id, 10);
return internalAccessList.update(res.locals.access, payload); return internalAccessList.update(res.locals.access, payload);
}) })
.then((result) => { .then((result) => {
res.status(200) res.status(200).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}) })
@ -137,10 +139,10 @@ router
* Delete and existing access-list * Delete and existing access-list
*/ */
.delete((req, res, next) => { .delete((req, res, next) => {
internalAccessList.delete(res.locals.access, {id: parseInt(req.params.list_id, 10)}) internalAccessList
.delete(res.locals.access, { id: parseInt(req.params.list_id, 10) })
.then((result) => { .then((result) => {
res.status(200) res.status(200).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}); });

View file

@ -1,13 +1,13 @@
const express = require('express'); const express = require('express');
const validator = require('../../../lib/validator'); const validator = require('../../../lib/validator');
const jwtdecode = require('../../../lib/express/jwt-decode'); const jwtdecode = require('../../../lib/express/jwt-decode');
const internalCertificate = require('../../../internal/certificate'); const internalCertificate = require('../../../internal/certificate');
const apiValidator = require('../../../lib/validator/api'); const apiValidator = require('../../../lib/validator/api');
let router = express.Router({ const router = express.Router({
caseSensitive: true, caseSensitive: true,
strict: true, strict: true,
mergeParams: true mergeParams: true,
}); });
/** /**
@ -26,26 +26,28 @@ router
* Retrieve all certificates * Retrieve all certificates
*/ */
.get((req, res, next) => { .get((req, res, next) => {
validator({ validator(
additionalProperties: false, {
properties: { additionalProperties: false,
expand: { properties: {
$ref: 'definitions#/definitions/expand' expand: {
$ref: 'definitions#/definitions/expand',
},
query: {
$ref: 'definitions#/definitions/query',
},
}, },
query: { },
$ref: 'definitions#/definitions/query' {
} expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null,
} query: typeof req.query.query === 'string' ? req.query.query : null,
}, { },
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null), )
query: (typeof req.query.query === 'string' ? req.query.query : null)
})
.then((data) => { .then((data) => {
return internalCertificate.getAll(res.locals.access, data.expand, data.query); return internalCertificate.getAll(res.locals.access, data.expand, data.query);
}) })
.then((rows) => { .then((rows) => {
res.status(200) res.status(200).send(rows);
.send(rows);
}) })
.catch(next); .catch(next);
}) })
@ -56,14 +58,13 @@ router
* Create a new certificate * Create a new certificate
*/ */
.post((req, res, next) => { .post((req, res, next) => {
apiValidator({$ref: 'endpoints/certificates#/links/1/schema'}, req.body) apiValidator({ $ref: 'endpoints/certificates#/links/1/schema' }, req.body)
.then((payload) => { .then((payload) => {
req.setTimeout(900000); // 15 minutes timeout req.setTimeout(900000); // 15 minutes timeout
return internalCertificate.create(res.locals.access, payload); return internalCertificate.create(res.locals.access, payload);
}) })
.then((result) => { .then((result) => {
res.status(201) res.status(201).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}); });
@ -80,16 +81,16 @@ router
}) })
.all(jwtdecode()) .all(jwtdecode())
/** /**
* GET /api/nginx/certificates/test-http * GET /api/nginx/certificates/test-http
* *
* Test HTTP challenge for domains * Test HTTP challenge for domains
*/ */
.get((req, res, next) => { .get((req, res, next) => {
internalCertificate.testHttpsChallenge(res.locals.access, JSON.parse(req.query.domains)) internalCertificate
.testHttpsChallenge(res.locals.access, JSON.parse(req.query.domains))
.then((result) => { .then((result) => {
res.status(200) res.status(200).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}); });
@ -112,30 +113,32 @@ router
* Retrieve a specific certificate * Retrieve a specific certificate
*/ */
.get((req, res, next) => { .get((req, res, next) => {
validator({ validator(
required: ['certificate_id'], {
additionalProperties: false, required: ['certificate_id'],
properties: { additionalProperties: false,
certificate_id: { properties: {
$ref: 'definitions#/definitions/id' certificate_id: {
$ref: 'definitions#/definitions/id',
},
expand: {
$ref: 'definitions#/definitions/expand',
},
}, },
expand: { },
$ref: 'definitions#/definitions/expand' {
} certificate_id: req.params.certificate_id,
} expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null,
}, { },
certificate_id: req.params.certificate_id, )
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null)
})
.then((data) => { .then((data) => {
return internalCertificate.get(res.locals.access, { return internalCertificate.get(res.locals.access, {
id: parseInt(data.certificate_id, 10), id: parseInt(data.certificate_id, 10),
expand: data.expand expand: data.expand,
}); });
}) })
.then((row) => { .then((row) => {
res.status(200) res.status(200).send(row);
.send(row);
}) })
.catch(next); .catch(next);
}) })
@ -146,14 +149,13 @@ router
* Update and existing certificate * Update and existing certificate
*/ */
.put((req, res, next) => { .put((req, res, next) => {
apiValidator({$ref: 'endpoints/certificates#/links/2/schema'}, req.body) apiValidator({ $ref: 'endpoints/certificates#/links/2/schema' }, req.body)
.then((payload) => { .then((payload) => {
payload.id = parseInt(req.params.certificate_id, 10); payload.id = parseInt(req.params.certificate_id, 10);
return internalCertificate.update(res.locals.access, payload); return internalCertificate.update(res.locals.access, payload);
}) })
.then((result) => { .then((result) => {
res.status(200) res.status(200).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}) })
@ -164,10 +166,10 @@ router
* Update and existing certificate * Update and existing certificate
*/ */
.delete((req, res, next) => { .delete((req, res, next) => {
internalCertificate.delete(res.locals.access, {id: parseInt(req.params.certificate_id, 10)}) internalCertificate
.delete(res.locals.access, { id: parseInt(req.params.certificate_id, 10) })
.then((result) => { .then((result) => {
res.status(200) res.status(200).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}); });
@ -191,16 +193,15 @@ router
*/ */
.post((req, res, next) => { .post((req, res, next) => {
if (!req.files) { if (!req.files) {
res.status(400) res.status(400).send({ error: 'No files were uploaded' });
.send({error: 'No files were uploaded'});
} else { } else {
internalCertificate.upload(res.locals.access, { internalCertificate
id: parseInt(req.params.certificate_id, 10), .upload(res.locals.access, {
files: req.files id: parseInt(req.params.certificate_id, 10),
}) files: req.files,
})
.then((result) => { .then((result) => {
res.status(200) res.status(200).send(result);
.send(result);
}) })
.catch(next); .catch(next);
} }
@ -225,12 +226,12 @@ router
*/ */
.post((req, res, next) => { .post((req, res, next) => {
req.setTimeout(900000); // 15 minutes timeout req.setTimeout(900000); // 15 minutes timeout
internalCertificate.renew(res.locals.access, { internalCertificate
id: parseInt(req.params.certificate_id, 10) .renew(res.locals.access, {
}) id: parseInt(req.params.certificate_id, 10),
})
.then((result) => { .then((result) => {
res.status(200) res.status(200).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}); });
@ -253,12 +254,12 @@ router
* Renew certificate * Renew certificate
*/ */
.get((req, res, next) => { .get((req, res, next) => {
internalCertificate.download(res.locals.access, { internalCertificate
id: parseInt(req.params.certificate_id, 10) .download(res.locals.access, {
}) id: parseInt(req.params.certificate_id, 10),
})
.then((result) => { .then((result) => {
res.status(200) res.status(200).download(result.fileName);
.download(result.fileName);
}) })
.catch(next); .catch(next);
}); });
@ -282,15 +283,14 @@ router
*/ */
.post((req, res, next) => { .post((req, res, next) => {
if (!req.files) { if (!req.files) {
res.status(400) res.status(400).send({ error: 'No files were uploaded' });
.send({error: 'No files were uploaded'});
} else { } else {
internalCertificate.validate({ internalCertificate
files: req.files .validate({
}) files: req.files,
})
.then((result) => { .then((result) => {
res.status(200) res.status(200).send(result);
.send(result);
}) })
.catch(next); .catch(next);
} }

View file

@ -1,13 +1,13 @@
const express = require('express'); const express = require('express');
const validator = require('../../../lib/validator'); const validator = require('../../../lib/validator');
const jwtdecode = require('../../../lib/express/jwt-decode'); const jwtdecode = require('../../../lib/express/jwt-decode');
const internalDeadHost = require('../../../internal/dead-host'); const internalDeadHost = require('../../../internal/dead-host');
const apiValidator = require('../../../lib/validator/api'); const apiValidator = require('../../../lib/validator/api');
let router = express.Router({ const router = express.Router({
caseSensitive: true, caseSensitive: true,
strict: true, strict: true,
mergeParams: true mergeParams: true,
}); });
/** /**
@ -26,26 +26,28 @@ router
* Retrieve all dead-hosts * Retrieve all dead-hosts
*/ */
.get((req, res, next) => { .get((req, res, next) => {
validator({ validator(
additionalProperties: false, {
properties: { additionalProperties: false,
expand: { properties: {
$ref: 'definitions#/definitions/expand' expand: {
$ref: 'definitions#/definitions/expand',
},
query: {
$ref: 'definitions#/definitions/query',
},
}, },
query: { },
$ref: 'definitions#/definitions/query' {
} expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null,
} query: typeof req.query.query === 'string' ? req.query.query : null,
}, { },
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null), )
query: (typeof req.query.query === 'string' ? req.query.query : null)
})
.then((data) => { .then((data) => {
return internalDeadHost.getAll(res.locals.access, data.expand, data.query); return internalDeadHost.getAll(res.locals.access, data.expand, data.query);
}) })
.then((rows) => { .then((rows) => {
res.status(200) res.status(200).send(rows);
.send(rows);
}) })
.catch(next); .catch(next);
}) })
@ -56,13 +58,12 @@ router
* Create a new dead-host * Create a new dead-host
*/ */
.post((req, res, next) => { .post((req, res, next) => {
apiValidator({$ref: 'endpoints/dead-hosts#/links/1/schema'}, req.body) apiValidator({ $ref: 'endpoints/dead-hosts#/links/1/schema' }, req.body)
.then((payload) => { .then((payload) => {
return internalDeadHost.create(res.locals.access, payload); return internalDeadHost.create(res.locals.access, payload);
}) })
.then((result) => { .then((result) => {
res.status(201) res.status(201).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}); });
@ -85,30 +86,32 @@ router
* Retrieve a specific dead-host * Retrieve a specific dead-host
*/ */
.get((req, res, next) => { .get((req, res, next) => {
validator({ validator(
required: ['host_id'], {
additionalProperties: false, required: ['host_id'],
properties: { additionalProperties: false,
host_id: { properties: {
$ref: 'definitions#/definitions/id' host_id: {
$ref: 'definitions#/definitions/id',
},
expand: {
$ref: 'definitions#/definitions/expand',
},
}, },
expand: { },
$ref: 'definitions#/definitions/expand' {
} host_id: req.params.host_id,
} expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null,
}, { },
host_id: req.params.host_id, )
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null)
})
.then((data) => { .then((data) => {
return internalDeadHost.get(res.locals.access, { return internalDeadHost.get(res.locals.access, {
id: parseInt(data.host_id, 10), id: parseInt(data.host_id, 10),
expand: data.expand expand: data.expand,
}); });
}) })
.then((row) => { .then((row) => {
res.status(200) res.status(200).send(row);
.send(row);
}) })
.catch(next); .catch(next);
}) })
@ -119,14 +122,13 @@ router
* Update and existing dead-host * Update and existing dead-host
*/ */
.put((req, res, next) => { .put((req, res, next) => {
apiValidator({$ref: 'endpoints/dead-hosts#/links/2/schema'}, req.body) apiValidator({ $ref: 'endpoints/dead-hosts#/links/2/schema' }, req.body)
.then((payload) => { .then((payload) => {
payload.id = parseInt(req.params.host_id, 10); payload.id = parseInt(req.params.host_id, 10);
return internalDeadHost.update(res.locals.access, payload); return internalDeadHost.update(res.locals.access, payload);
}) })
.then((result) => { .then((result) => {
res.status(200) res.status(200).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}) })
@ -137,10 +139,10 @@ router
* Update and existing dead-host * Update and existing dead-host
*/ */
.delete((req, res, next) => { .delete((req, res, next) => {
internalDeadHost.delete(res.locals.access, {id: parseInt(req.params.host_id, 10)}) internalDeadHost
.delete(res.locals.access, { id: parseInt(req.params.host_id, 10) })
.then((result) => { .then((result) => {
res.status(200) res.status(200).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}); });
@ -161,10 +163,10 @@ router
* POST /api/nginx/dead-hosts/123/enable * POST /api/nginx/dead-hosts/123/enable
*/ */
.post((req, res, next) => { .post((req, res, next) => {
internalDeadHost.enable(res.locals.access, {id: parseInt(req.params.host_id, 10)}) internalDeadHost
.enable(res.locals.access, { id: parseInt(req.params.host_id, 10) })
.then((result) => { .then((result) => {
res.status(200) res.status(200).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}); });
@ -185,10 +187,10 @@ router
* POST /api/nginx/dead-hosts/123/disable * POST /api/nginx/dead-hosts/123/disable
*/ */
.post((req, res, next) => { .post((req, res, next) => {
internalDeadHost.disable(res.locals.access, {id: parseInt(req.params.host_id, 10)}) internalDeadHost
.disable(res.locals.access, { id: parseInt(req.params.host_id, 10) })
.then((result) => { .then((result) => {
res.status(200) res.status(200).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}); });

View file

@ -1,13 +1,13 @@
const express = require('express'); const express = require('express');
const validator = require('../../../lib/validator'); const validator = require('../../../lib/validator');
const jwtdecode = require('../../../lib/express/jwt-decode'); const jwtdecode = require('../../../lib/express/jwt-decode');
const internalProxyHost = require('../../../internal/proxy-host'); const internalProxyHost = require('../../../internal/proxy-host');
const apiValidator = require('../../../lib/validator/api'); const apiValidator = require('../../../lib/validator/api');
let router = express.Router({ const router = express.Router({
caseSensitive: true, caseSensitive: true,
strict: true, strict: true,
mergeParams: true mergeParams: true,
}); });
/** /**
@ -26,26 +26,28 @@ router
* Retrieve all proxy-hosts * Retrieve all proxy-hosts
*/ */
.get((req, res, next) => { .get((req, res, next) => {
validator({ validator(
additionalProperties: false, {
properties: { additionalProperties: false,
expand: { properties: {
$ref: 'definitions#/definitions/expand' expand: {
$ref: 'definitions#/definitions/expand',
},
query: {
$ref: 'definitions#/definitions/query',
},
}, },
query: { },
$ref: 'definitions#/definitions/query' {
} expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null,
} query: typeof req.query.query === 'string' ? req.query.query : null,
}, { },
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null), )
query: (typeof req.query.query === 'string' ? req.query.query : null)
})
.then((data) => { .then((data) => {
return internalProxyHost.getAll(res.locals.access, data.expand, data.query); return internalProxyHost.getAll(res.locals.access, data.expand, data.query);
}) })
.then((rows) => { .then((rows) => {
res.status(200) res.status(200).send(rows);
.send(rows);
}) })
.catch(next); .catch(next);
}) })
@ -56,13 +58,12 @@ router
* Create a new proxy-host * Create a new proxy-host
*/ */
.post((req, res, next) => { .post((req, res, next) => {
apiValidator({$ref: 'endpoints/proxy-hosts#/links/1/schema'}, req.body) apiValidator({ $ref: 'endpoints/proxy-hosts#/links/1/schema' }, req.body)
.then((payload) => { .then((payload) => {
return internalProxyHost.create(res.locals.access, payload); return internalProxyHost.create(res.locals.access, payload);
}) })
.then((result) => { .then((result) => {
res.status(201) res.status(201).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}); });
@ -85,30 +86,32 @@ router
* Retrieve a specific proxy-host * Retrieve a specific proxy-host
*/ */
.get((req, res, next) => { .get((req, res, next) => {
validator({ validator(
required: ['host_id'], {
additionalProperties: false, required: ['host_id'],
properties: { additionalProperties: false,
host_id: { properties: {
$ref: 'definitions#/definitions/id' host_id: {
$ref: 'definitions#/definitions/id',
},
expand: {
$ref: 'definitions#/definitions/expand',
},
}, },
expand: { },
$ref: 'definitions#/definitions/expand' {
} host_id: req.params.host_id,
} expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null,
}, { },
host_id: req.params.host_id, )
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null)
})
.then((data) => { .then((data) => {
return internalProxyHost.get(res.locals.access, { return internalProxyHost.get(res.locals.access, {
id: parseInt(data.host_id, 10), id: parseInt(data.host_id, 10),
expand: data.expand expand: data.expand,
}); });
}) })
.then((row) => { .then((row) => {
res.status(200) res.status(200).send(row);
.send(row);
}) })
.catch(next); .catch(next);
}) })
@ -119,14 +122,13 @@ router
* Update and existing proxy-host * Update and existing proxy-host
*/ */
.put((req, res, next) => { .put((req, res, next) => {
apiValidator({$ref: 'endpoints/proxy-hosts#/links/2/schema'}, req.body) apiValidator({ $ref: 'endpoints/proxy-hosts#/links/2/schema' }, req.body)
.then((payload) => { .then((payload) => {
payload.id = parseInt(req.params.host_id, 10); payload.id = parseInt(req.params.host_id, 10);
return internalProxyHost.update(res.locals.access, payload); return internalProxyHost.update(res.locals.access, payload);
}) })
.then((result) => { .then((result) => {
res.status(200) res.status(200).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}) })
@ -137,10 +139,10 @@ router
* Update and existing proxy-host * Update and existing proxy-host
*/ */
.delete((req, res, next) => { .delete((req, res, next) => {
internalProxyHost.delete(res.locals.access, {id: parseInt(req.params.host_id, 10)}) internalProxyHost
.delete(res.locals.access, { id: parseInt(req.params.host_id, 10) })
.then((result) => { .then((result) => {
res.status(200) res.status(200).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}); });
@ -161,10 +163,10 @@ router
* POST /api/nginx/proxy-hosts/123/enable * POST /api/nginx/proxy-hosts/123/enable
*/ */
.post((req, res, next) => { .post((req, res, next) => {
internalProxyHost.enable(res.locals.access, {id: parseInt(req.params.host_id, 10)}) internalProxyHost
.enable(res.locals.access, { id: parseInt(req.params.host_id, 10) })
.then((result) => { .then((result) => {
res.status(200) res.status(200).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}); });
@ -185,10 +187,10 @@ router
* POST /api/nginx/proxy-hosts/123/disable * POST /api/nginx/proxy-hosts/123/disable
*/ */
.post((req, res, next) => { .post((req, res, next) => {
internalProxyHost.disable(res.locals.access, {id: parseInt(req.params.host_id, 10)}) internalProxyHost
.disable(res.locals.access, { id: parseInt(req.params.host_id, 10) })
.then((result) => { .then((result) => {
res.status(200) res.status(200).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}); });

View file

@ -1,13 +1,13 @@
const express = require('express'); const express = require('express');
const validator = require('../../../lib/validator'); const validator = require('../../../lib/validator');
const jwtdecode = require('../../../lib/express/jwt-decode'); const jwtdecode = require('../../../lib/express/jwt-decode');
const internalRedirectionHost = require('../../../internal/redirection-host'); const internalRedirectionHost = require('../../../internal/redirection-host');
const apiValidator = require('../../../lib/validator/api'); const apiValidator = require('../../../lib/validator/api');
let router = express.Router({ const router = express.Router({
caseSensitive: true, caseSensitive: true,
strict: true, strict: true,
mergeParams: true mergeParams: true,
}); });
/** /**
@ -26,26 +26,28 @@ router
* Retrieve all redirection-hosts * Retrieve all redirection-hosts
*/ */
.get((req, res, next) => { .get((req, res, next) => {
validator({ validator(
additionalProperties: false, {
properties: { additionalProperties: false,
expand: { properties: {
$ref: 'definitions#/definitions/expand' expand: {
$ref: 'definitions#/definitions/expand',
},
query: {
$ref: 'definitions#/definitions/query',
},
}, },
query: { },
$ref: 'definitions#/definitions/query' {
} expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null,
} query: typeof req.query.query === 'string' ? req.query.query : null,
}, { },
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null), )
query: (typeof req.query.query === 'string' ? req.query.query : null)
})
.then((data) => { .then((data) => {
return internalRedirectionHost.getAll(res.locals.access, data.expand, data.query); return internalRedirectionHost.getAll(res.locals.access, data.expand, data.query);
}) })
.then((rows) => { .then((rows) => {
res.status(200) res.status(200).send(rows);
.send(rows);
}) })
.catch(next); .catch(next);
}) })
@ -56,13 +58,12 @@ router
* Create a new redirection-host * Create a new redirection-host
*/ */
.post((req, res, next) => { .post((req, res, next) => {
apiValidator({$ref: 'endpoints/redirection-hosts#/links/1/schema'}, req.body) apiValidator({ $ref: 'endpoints/redirection-hosts#/links/1/schema' }, req.body)
.then((payload) => { .then((payload) => {
return internalRedirectionHost.create(res.locals.access, payload); return internalRedirectionHost.create(res.locals.access, payload);
}) })
.then((result) => { .then((result) => {
res.status(201) res.status(201).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}); });
@ -85,30 +86,32 @@ router
* Retrieve a specific redirection-host * Retrieve a specific redirection-host
*/ */
.get((req, res, next) => { .get((req, res, next) => {
validator({ validator(
required: ['host_id'], {
additionalProperties: false, required: ['host_id'],
properties: { additionalProperties: false,
host_id: { properties: {
$ref: 'definitions#/definitions/id' host_id: {
$ref: 'definitions#/definitions/id',
},
expand: {
$ref: 'definitions#/definitions/expand',
},
}, },
expand: { },
$ref: 'definitions#/definitions/expand' {
} host_id: req.params.host_id,
} expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null,
}, { },
host_id: req.params.host_id, )
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null)
})
.then((data) => { .then((data) => {
return internalRedirectionHost.get(res.locals.access, { return internalRedirectionHost.get(res.locals.access, {
id: parseInt(data.host_id, 10), id: parseInt(data.host_id, 10),
expand: data.expand expand: data.expand,
}); });
}) })
.then((row) => { .then((row) => {
res.status(200) res.status(200).send(row);
.send(row);
}) })
.catch(next); .catch(next);
}) })
@ -119,14 +122,13 @@ router
* Update and existing redirection-host * Update and existing redirection-host
*/ */
.put((req, res, next) => { .put((req, res, next) => {
apiValidator({$ref: 'endpoints/redirection-hosts#/links/2/schema'}, req.body) apiValidator({ $ref: 'endpoints/redirection-hosts#/links/2/schema' }, req.body)
.then((payload) => { .then((payload) => {
payload.id = parseInt(req.params.host_id, 10); payload.id = parseInt(req.params.host_id, 10);
return internalRedirectionHost.update(res.locals.access, payload); return internalRedirectionHost.update(res.locals.access, payload);
}) })
.then((result) => { .then((result) => {
res.status(200) res.status(200).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}) })
@ -137,10 +139,10 @@ router
* Update and existing redirection-host * Update and existing redirection-host
*/ */
.delete((req, res, next) => { .delete((req, res, next) => {
internalRedirectionHost.delete(res.locals.access, {id: parseInt(req.params.host_id, 10)}) internalRedirectionHost
.delete(res.locals.access, { id: parseInt(req.params.host_id, 10) })
.then((result) => { .then((result) => {
res.status(200) res.status(200).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}); });
@ -161,10 +163,10 @@ router
* POST /api/nginx/redirection-hosts/123/enable * POST /api/nginx/redirection-hosts/123/enable
*/ */
.post((req, res, next) => { .post((req, res, next) => {
internalRedirectionHost.enable(res.locals.access, {id: parseInt(req.params.host_id, 10)}) internalRedirectionHost
.enable(res.locals.access, { id: parseInt(req.params.host_id, 10) })
.then((result) => { .then((result) => {
res.status(200) res.status(200).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}); });
@ -185,10 +187,10 @@ router
* POST /api/nginx/redirection-hosts/123/disable * POST /api/nginx/redirection-hosts/123/disable
*/ */
.post((req, res, next) => { .post((req, res, next) => {
internalRedirectionHost.disable(res.locals.access, {id: parseInt(req.params.host_id, 10)}) internalRedirectionHost
.disable(res.locals.access, { id: parseInt(req.params.host_id, 10) })
.then((result) => { .then((result) => {
res.status(200) res.status(200).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}); });

View file

@ -1,13 +1,13 @@
const express = require('express'); const express = require('express');
const validator = require('../../../lib/validator'); const validator = require('../../../lib/validator');
const jwtdecode = require('../../../lib/express/jwt-decode'); const jwtdecode = require('../../../lib/express/jwt-decode');
const internalStream = require('../../../internal/stream'); const internalStream = require('../../../internal/stream');
const apiValidator = require('../../../lib/validator/api'); const apiValidator = require('../../../lib/validator/api');
let router = express.Router({ const router = express.Router({
caseSensitive: true, caseSensitive: true,
strict: true, strict: true,
mergeParams: true mergeParams: true,
}); });
/** /**
@ -26,26 +26,28 @@ router
* Retrieve all streams * Retrieve all streams
*/ */
.get((req, res, next) => { .get((req, res, next) => {
validator({ validator(
additionalProperties: false, {
properties: { additionalProperties: false,
expand: { properties: {
$ref: 'definitions#/definitions/expand' expand: {
$ref: 'definitions#/definitions/expand',
},
query: {
$ref: 'definitions#/definitions/query',
},
}, },
query: { },
$ref: 'definitions#/definitions/query' {
} expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null,
} query: typeof req.query.query === 'string' ? req.query.query : null,
}, { },
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null), )
query: (typeof req.query.query === 'string' ? req.query.query : null)
})
.then((data) => { .then((data) => {
return internalStream.getAll(res.locals.access, data.expand, data.query); return internalStream.getAll(res.locals.access, data.expand, data.query);
}) })
.then((rows) => { .then((rows) => {
res.status(200) res.status(200).send(rows);
.send(rows);
}) })
.catch(next); .catch(next);
}) })
@ -56,13 +58,12 @@ router
* Create a new stream * Create a new stream
*/ */
.post((req, res, next) => { .post((req, res, next) => {
apiValidator({$ref: 'endpoints/streams#/links/1/schema'}, req.body) apiValidator({ $ref: 'endpoints/streams#/links/1/schema' }, req.body)
.then((payload) => { .then((payload) => {
return internalStream.create(res.locals.access, payload); return internalStream.create(res.locals.access, payload);
}) })
.then((result) => { .then((result) => {
res.status(201) res.status(201).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}); });
@ -85,30 +86,32 @@ router
* Retrieve a specific stream * Retrieve a specific stream
*/ */
.get((req, res, next) => { .get((req, res, next) => {
validator({ validator(
required: ['stream_id'], {
additionalProperties: false, required: ['stream_id'],
properties: { additionalProperties: false,
stream_id: { properties: {
$ref: 'definitions#/definitions/id' stream_id: {
$ref: 'definitions#/definitions/id',
},
expand: {
$ref: 'definitions#/definitions/expand',
},
}, },
expand: { },
$ref: 'definitions#/definitions/expand' {
} stream_id: req.params.stream_id,
} expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null,
}, { },
stream_id: req.params.stream_id, )
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null)
})
.then((data) => { .then((data) => {
return internalStream.get(res.locals.access, { return internalStream.get(res.locals.access, {
id: parseInt(data.stream_id, 10), id: parseInt(data.stream_id, 10),
expand: data.expand expand: data.expand,
}); });
}) })
.then((row) => { .then((row) => {
res.status(200) res.status(200).send(row);
.send(row);
}) })
.catch(next); .catch(next);
}) })
@ -119,14 +122,13 @@ router
* Update and existing stream * Update and existing stream
*/ */
.put((req, res, next) => { .put((req, res, next) => {
apiValidator({$ref: 'endpoints/streams#/links/2/schema'}, req.body) apiValidator({ $ref: 'endpoints/streams#/links/2/schema' }, req.body)
.then((payload) => { .then((payload) => {
payload.id = parseInt(req.params.stream_id, 10); payload.id = parseInt(req.params.stream_id, 10);
return internalStream.update(res.locals.access, payload); return internalStream.update(res.locals.access, payload);
}) })
.then((result) => { .then((result) => {
res.status(200) res.status(200).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}) })
@ -137,10 +139,10 @@ router
* Update and existing stream * Update and existing stream
*/ */
.delete((req, res, next) => { .delete((req, res, next) => {
internalStream.delete(res.locals.access, {id: parseInt(req.params.stream_id, 10)}) internalStream
.delete(res.locals.access, { id: parseInt(req.params.stream_id, 10) })
.then((result) => { .then((result) => {
res.status(200) res.status(200).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}); });
@ -161,10 +163,10 @@ router
* POST /api/nginx/streams/123/enable * POST /api/nginx/streams/123/enable
*/ */
.post((req, res, next) => { .post((req, res, next) => {
internalStream.enable(res.locals.access, {id: parseInt(req.params.host_id, 10)}) internalStream
.enable(res.locals.access, { id: parseInt(req.params.host_id, 10) })
.then((result) => { .then((result) => {
res.status(200) res.status(200).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}); });
@ -185,10 +187,10 @@ router
* POST /api/nginx/streams/123/disable * POST /api/nginx/streams/123/disable
*/ */
.post((req, res, next) => { .post((req, res, next) => {
internalStream.disable(res.locals.access, {id: parseInt(req.params.host_id, 10)}) internalStream
.disable(res.locals.access, { id: parseInt(req.params.host_id, 10) })
.then((result) => { .then((result) => {
res.status(200) res.status(200).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}); });

View file

@ -1,11 +1,11 @@
const express = require('express'); const express = require('express');
const jwtdecode = require('../../lib/express/jwt-decode'); const jwtdecode = require('../../lib/express/jwt-decode');
const internalReport = require('../../internal/report'); const internalReport = require('../../internal/report');
let router = express.Router({ const router = express.Router({
caseSensitive: true, caseSensitive: true,
strict: true, strict: true,
mergeParams: true mergeParams: true,
}); });
router router
@ -18,10 +18,10 @@ router
* GET /reports/hosts * GET /reports/hosts
*/ */
.get(jwtdecode(), (req, res, next) => { .get(jwtdecode(), (req, res, next) => {
internalReport.getHostsReport(res.locals.access) internalReport
.getHostsReport(res.locals.access)
.then((data) => { .then((data) => {
res.status(200) res.status(200).send(data);
.send(data);
}) })
.catch(next); .catch(next);
}); });

View file

@ -1,11 +1,11 @@
const express = require('express'); const express = require('express');
const swaggerJSON = require('../../doc/api.swagger.json'); const swaggerJSON = require('../../doc/api.swagger.json');
const PACKAGE = require('../../package.json'); const PACKAGE = require('../../package.json');
let router = express.Router({ const router = express.Router({
caseSensitive: true, caseSensitive: true,
strict: true, strict: true,
mergeParams: true mergeParams: true,
}); });
router router
@ -17,7 +17,7 @@ router
/** /**
* GET /schema * GET /schema
*/ */
.get((req, res/*, next*/) => { .get((req, res /*, next */) => {
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'];
@ -28,7 +28,7 @@ router
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);
}); });

View file

@ -1,13 +1,13 @@
const express = require('express'); const express = require('express');
const validator = require('../../lib/validator'); const validator = require('../../lib/validator');
const jwtdecode = require('../../lib/express/jwt-decode'); const jwtdecode = require('../../lib/express/jwt-decode');
const internalSetting = require('../../internal/setting'); const internalSetting = require('../../internal/setting');
const apiValidator = require('../../lib/validator/api'); const apiValidator = require('../../lib/validator/api');
let router = express.Router({ const router = express.Router({
caseSensitive: true, caseSensitive: true,
strict: true, strict: true,
mergeParams: true mergeParams: true,
}); });
/** /**
@ -26,10 +26,10 @@ router
* Retrieve all settings * Retrieve all settings
*/ */
.get((req, res, next) => { .get((req, res, next) => {
internalSetting.getAll(res.locals.access) internalSetting
.getAll(res.locals.access)
.then((rows) => { .then((rows) => {
res.status(200) res.status(200).send(rows);
.send(rows);
}) })
.catch(next); .catch(next);
}); });
@ -52,25 +52,27 @@ router
* Retrieve a specific setting * Retrieve a specific setting
*/ */
.get((req, res, next) => { .get((req, res, next) => {
validator({ validator(
required: ['setting_id'], {
additionalProperties: false, required: ['setting_id'],
properties: { additionalProperties: false,
setting_id: { properties: {
$ref: 'definitions#/definitions/setting_id' setting_id: {
} $ref: 'definitions#/definitions/setting_id',
} },
}, { },
setting_id: req.params.setting_id },
}) {
setting_id: req.params.setting_id,
},
)
.then((data) => { .then((data) => {
return internalSetting.get(res.locals.access, { return internalSetting.get(res.locals.access, {
id: data.setting_id id: data.setting_id,
}); });
}) })
.then((row) => { .then((row) => {
res.status(200) res.status(200).send(row);
.send(row);
}) })
.catch(next); .catch(next);
}) })
@ -81,14 +83,13 @@ router
* Update and existing setting * Update and existing setting
*/ */
.put((req, res, next) => { .put((req, res, next) => {
apiValidator({$ref: 'endpoints/settings#/links/1/schema'}, req.body) apiValidator({ $ref: 'endpoints/settings#/links/1/schema' }, req.body)
.then((payload) => { .then((payload) => {
payload.id = req.params.setting_id; payload.id = req.params.setting_id;
return internalSetting.update(res.locals.access, payload); return internalSetting.update(res.locals.access, payload);
}) })
.then((result) => { .then((result) => {
res.status(200) res.status(200).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}); });

View file

@ -1,12 +1,12 @@
const express = require('express'); const express = require('express');
const jwtdecode = require('../../lib/express/jwt-decode'); const jwtdecode = require('../../lib/express/jwt-decode');
const internalToken = require('../../internal/token'); const internalToken = require('../../internal/token');
const apiValidator = require('../../lib/validator/api'); const apiValidator = require('../../lib/validator/api');
let router = express.Router({ const router = express.Router({
caseSensitive: true, caseSensitive: true,
strict: true, strict: true,
mergeParams: true mergeParams: true,
}); });
router router
@ -23,13 +23,13 @@ router
* for services like Job board and Worker. * for services like Job board and Worker.
*/ */
.get(jwtdecode(), (req, res, next) => { .get(jwtdecode(), (req, res, next) => {
internalToken.getFreshToken(res.locals.access, { internalToken
expiry: (typeof req.query.expiry !== 'undefined' ? req.query.expiry : null), .getFreshToken(res.locals.access, {
scope: (typeof req.query.scope !== 'undefined' ? req.query.scope : null) expiry: typeof req.query.expiry !== 'undefined' ? req.query.expiry : null,
}) scope: typeof req.query.scope !== 'undefined' ? req.query.scope : null,
})
.then((data) => { .then((data) => {
res.status(200) res.status(200).send(data);
.send(data);
}) })
.catch(next); .catch(next);
}) })
@ -40,13 +40,12 @@ router
* Create a new Token * Create a new Token
*/ */
.post((req, res, next) => { .post((req, res, next) => {
apiValidator({$ref: 'endpoints/tokens#/links/0/schema'}, req.body) apiValidator({ $ref: 'endpoints/tokens#/links/0/schema' }, req.body)
.then((payload) => { .then((payload) => {
return internalToken.getTokenFromEmail(payload); return internalToken.getTokenFromEmail(payload);
}) })
.then((data) => { .then((data) => {
res.status(200) res.status(200).send(data);
.send(data);
}) })
.catch(next); .catch(next);
}); });

View file

@ -1,14 +1,14 @@
const express = require('express'); const express = require('express');
const validator = require('../../lib/validator'); const validator = require('../../lib/validator');
const jwtdecode = require('../../lib/express/jwt-decode'); const jwtdecode = require('../../lib/express/jwt-decode');
const userIdFromMe = require('../../lib/express/user-id-from-me'); const userIdFromMe = require('../../lib/express/user-id-from-me');
const internalUser = require('../../internal/user'); const internalUser = require('../../internal/user');
const apiValidator = require('../../lib/validator/api'); const apiValidator = require('../../lib/validator/api');
let router = express.Router({ const router = express.Router({
caseSensitive: true, caseSensitive: true,
strict: true, strict: true,
mergeParams: true mergeParams: true,
}); });
/** /**
@ -27,26 +27,28 @@ router
* Retrieve all users * Retrieve all users
*/ */
.get((req, res, next) => { .get((req, res, next) => {
validator({ validator(
additionalProperties: false, {
properties: { additionalProperties: false,
expand: { properties: {
$ref: 'definitions#/definitions/expand' expand: {
$ref: 'definitions#/definitions/expand',
},
query: {
$ref: 'definitions#/definitions/query',
},
}, },
query: { },
$ref: 'definitions#/definitions/query' {
} expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null,
} query: typeof req.query.query === 'string' ? req.query.query : null,
}, { },
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null), )
query: (typeof req.query.query === 'string' ? req.query.query : null)
})
.then((data) => { .then((data) => {
return internalUser.getAll(res.locals.access, data.expand, data.query); return internalUser.getAll(res.locals.access, data.expand, data.query);
}) })
.then((users) => { .then((users) => {
res.status(200) res.status(200).send(users);
.send(users);
}) })
.catch(next); .catch(next);
}) })
@ -57,13 +59,12 @@ router
* Create a new User * Create a new User
*/ */
.post((req, res, next) => { .post((req, res, next) => {
apiValidator({$ref: 'endpoints/users#/links/1/schema'}, req.body) apiValidator({ $ref: 'endpoints/users#/links/1/schema' }, req.body)
.then((payload) => { .then((payload) => {
return internalUser.create(res.locals.access, payload); return internalUser.create(res.locals.access, payload);
}) })
.then((result) => { .then((result) => {
res.status(201) res.status(201).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}); });
@ -87,31 +88,33 @@ router
* Retrieve a specific user * Retrieve a specific user
*/ */
.get((req, res, next) => { .get((req, res, next) => {
validator({ validator(
required: ['user_id'], {
additionalProperties: false, required: ['user_id'],
properties: { additionalProperties: false,
user_id: { properties: {
$ref: 'definitions#/definitions/id' user_id: {
$ref: 'definitions#/definitions/id',
},
expand: {
$ref: 'definitions#/definitions/expand',
},
}, },
expand: { },
$ref: 'definitions#/definitions/expand' {
} user_id: req.params.user_id,
} expand: typeof req.query.expand === 'string' ? req.query.expand.split(',') : null,
}, { },
user_id: req.params.user_id, )
expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null)
})
.then((data) => { .then((data) => {
return internalUser.get(res.locals.access, { return internalUser.get(res.locals.access, {
id: data.user_id, id: data.user_id,
expand: data.expand, expand: data.expand,
omit: internalUser.getUserOmisionsByAccess(res.locals.access, data.user_id) omit: internalUser.getUserOmisionsByAccess(res.locals.access, data.user_id),
}); });
}) })
.then((user) => { .then((user) => {
res.status(200) res.status(200).send(user);
.send(user);
}) })
.catch(next); .catch(next);
}) })
@ -122,14 +125,13 @@ router
* Update and existing user * Update and existing user
*/ */
.put((req, res, next) => { .put((req, res, next) => {
apiValidator({$ref: 'endpoints/users#/links/2/schema'}, req.body) apiValidator({ $ref: 'endpoints/users#/links/2/schema' }, req.body)
.then((payload) => { .then((payload) => {
payload.id = req.params.user_id; payload.id = req.params.user_id;
return internalUser.update(res.locals.access, payload); return internalUser.update(res.locals.access, payload);
}) })
.then((result) => { .then((result) => {
res.status(200) res.status(200).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}) })
@ -140,10 +142,10 @@ router
* Update and existing user * Update and existing user
*/ */
.delete((req, res, next) => { .delete((req, res, next) => {
internalUser.delete(res.locals.access, {id: req.params.user_id}) internalUser
.delete(res.locals.access, { id: req.params.user_id })
.then((result) => { .then((result) => {
res.status(200) res.status(200).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}); });
@ -167,14 +169,13 @@ router
* Update password for a user * Update password for a user
*/ */
.put((req, res, next) => { .put((req, res, next) => {
apiValidator({$ref: 'endpoints/users#/links/4/schema'}, req.body) apiValidator({ $ref: 'endpoints/users#/links/4/schema' }, req.body)
.then((payload) => { .then((payload) => {
payload.id = req.params.user_id; payload.id = req.params.user_id;
return internalUser.setPassword(res.locals.access, payload); return internalUser.setPassword(res.locals.access, payload);
}) })
.then((result) => { .then((result) => {
res.status(201) res.status(201).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}); });
@ -198,14 +199,13 @@ router
* Set some or all permissions for a user * Set some or all permissions for a user
*/ */
.put((req, res, next) => { .put((req, res, next) => {
apiValidator({$ref: 'endpoints/users#/links/5/schema'}, req.body) apiValidator({ $ref: 'endpoints/users#/links/5/schema' }, req.body)
.then((payload) => { .then((payload) => {
payload.id = req.params.user_id; payload.id = req.params.user_id;
return internalUser.setPermissions(res.locals.access, payload); return internalUser.setPermissions(res.locals.access, payload);
}) })
.then((result) => { .then((result) => {
res.status(201) res.status(201).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}); });
@ -228,10 +228,10 @@ router
* Log in as a user * Log in as a user
*/ */
.post((req, res, next) => { .post((req, res, next) => {
internalUser.loginAs(res.locals.access, {id: parseInt(req.params.user_id, 10)}) internalUser
.loginAs(res.locals.access, { id: parseInt(req.params.user_id, 10) })
.then((result) => { .then((result) => {
res.status(201) res.status(201).send(result);
.send(result);
}) })
.catch(next); .catch(next);
}); });

View file

@ -1,12 +1,12 @@
const config = require('./lib/config'); const config = require('./lib/config');
const logger = require('./logger').setup; const logger = require('./logger').setup;
const certificateModel = require('./models/certificate'); const certificateModel = require('./models/certificate');
const userModel = require('./models/user'); const userModel = require('./models/user');
const userPermissionModel = require('./models/user_permission'); const userPermissionModel = require('./models/user_permission');
const utils = require('./lib/utils'); const utils = require('./lib/utils');
const authModel = require('./models/auth'); const authModel = require('./models/auth');
const settingModel = require('./models/setting'); const settingModel = require('./models/setting');
const certbot = require('./lib/certbot'); const certbot = require('./lib/certbot');
/** /**
* 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
@ -24,13 +24,13 @@ const setupDefaultUser = () => {
// Create a new user and set password // Create a new user and set password
logger.info('Creating a new user: admin@example.com with password: iArhP1j7p1P6TA92FA2FMbbUGYqwcYzxC4AVEe12Wbi94FY9gNN62aKyF1shrvG4NycjjX9KfmDQiwkLZH1ZDR9xMjiG2QmoHXi'); logger.info('Creating a new user: admin@example.com with password: iArhP1j7p1P6TA92FA2FMbbUGYqwcYzxC4AVEe12Wbi94FY9gNN62aKyF1shrvG4NycjjX9KfmDQiwkLZH1ZDR9xMjiG2QmoHXi');
let data = { const data = {
is_deleted: 0, is_deleted: 0,
email: 'admin@example.com', email: 'admin@example.com',
name: 'Administrator', name: 'Administrator',
nickname: 'Admin', nickname: 'Admin',
avatar: '', avatar: '',
roles: ['admin'], roles: ['admin'],
}; };
return userModel return userModel
@ -41,20 +41,20 @@ const setupDefaultUser = () => {
.query() .query()
.insert({ .insert({
user_id: user.id, user_id: user.id,
type: 'password', type: 'password',
secret: 'iArhP1j7p1P6TA92FA2FMbbUGYqwcYzxC4AVEe12Wbi94FY9gNN62aKyF1shrvG4NycjjX9KfmDQiwkLZH1ZDR9xMjiG2QmoHXi', secret: 'iArhP1j7p1P6TA92FA2FMbbUGYqwcYzxC4AVEe12Wbi94FY9gNN62aKyF1shrvG4NycjjX9KfmDQiwkLZH1ZDR9xMjiG2QmoHXi',
meta: {}, meta: {},
}) })
.then(() => { .then(() => {
return 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',
}); });
}); });
}) })
@ -76,18 +76,18 @@ const setupDefaultSettings = () => {
return settingModel return settingModel
.query() .query()
.select(settingModel.raw('COUNT(`id`) as `count`')) .select(settingModel.raw('COUNT(`id`) as `count`'))
.where({id: 'default-site'}) .where({ id: 'default-site' })
.first() .first()
.then((row) => { .then((row) => {
if (!row.count) { if (!row.count) {
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(() => { .then(() => {
logger.info('Default settings added'); logger.info('Default settings added');
@ -111,8 +111,8 @@ const setupCertbotPlugins = () => {
.andWhere('provider', 'letsencrypt') .andWhere('provider', 'letsencrypt')
.then((certificates) => { .then((certificates) => {
if (certificates && certificates.length) { if (certificates && certificates.length) {
let plugins = []; const plugins = [];
let promises = []; const promises = [];
certificates.map(function (certificate) { certificates.map(function (certificate) {
if (certificate.meta && certificate.meta.dns_challenge === true) { if (certificate.meta && certificate.meta.dns_challenge === true) {
@ -123,27 +123,23 @@ const setupCertbotPlugins = () => {
// Make sure credentials file exists // Make sure credentials file exists
const credentials_loc = '/data/tls/certbot/credentials/credentials-' + certificate.id; const credentials_loc = '/data/tls/certbot/credentials/credentials-' + certificate.id;
// Escape single quotes and backslashes // Escape single quotes and backslashes
const escapedCredentials = certificate.meta.dns_provider_credentials.replaceAll('\'', '\\\'').replaceAll('\\', '\\\\'); const escapedCredentials = certificate.meta.dns_provider_credentials.replaceAll("'", "\\'").replaceAll('\\', '\\\\');
const credentials_cmd = '[ -f \'' + credentials_loc + '\' ] || { mkdir -p /data/tls/certbot/credentials 2> /dev/null; echo \'' + escapedCredentials + '\' > \'' + credentials_loc + '\' && chmod 600 \'' + credentials_loc + '\'; }'; const credentials_cmd = "[ -f '" + credentials_loc + "' ] || { mkdir -p /data/tls/certbot/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 certbot.installPlugins(plugins) return certbot.installPlugins(plugins).then(() => {
.then(() => { if (promises.length) {
if (promises.length) { return Promise.all(promises).then(() => {
return Promise.all(promises) logger.info('Added Certbot plugins ' + plugins.join(', '));
.then(() => { });
logger.info('Added Certbot plugins ' + plugins.join(', ')); }
}); });
}
});
} }
}); });
}; };
module.exports = function () { module.exports = function () {
return setupDefaultUser() return setupDefaultUser().then(setupDefaultSettings).then(setupCertbotPlugins);
.then(setupDefaultSettings)
.then(setupCertbotPlugins);
}; };

View file

@ -1,6 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
const fs = require('fs'); const fs = require('fs');
const sqlite3 = require('sqlite3'); const sqlite3 = require('sqlite3');
if (fs.existsSync(process.env.DB_SQLITE_FILE)) { if (fs.existsSync(process.env.DB_SQLITE_FILE)) {

View file

@ -4,7 +4,7 @@
"description": "A beautiful interface for creating Nginx endpoints", "description": "A beautiful interface for creating Nginx endpoints",
"main": "js/index.js", "main": "js/index.js",
"dependencies": { "dependencies": {
"@babel/core": "7.24.3", "@babel/core": "7.24.4",
"babel-core": "6.26.3", "babel-core": "6.26.3",
"babel-loader": "8.3.0", "babel-loader": "8.3.0",
"babel-preset-env": "1.7.0", "babel-preset-env": "1.7.0",
@ -31,7 +31,7 @@
"nodemon": "3.1.0", "nodemon": "3.1.0",
"numeral": "2.0.6", "numeral": "2.0.6",
"sass-loader": "10.5.2", "sass-loader": "10.5.2",
"style-loader": "3.3.4", "style-loader": "4.0.0",
"tabler-ui": "git+https://github.com/tabler/tabler.git#00f78ad823311bc3ad974ac3e5b0126198f0a813", "tabler-ui": "git+https://github.com/tabler/tabler.git#00f78ad823311bc3ad974ac3e5b0126198f0a813",
"underscore": "1.13.6", "underscore": "1.13.6",
"webpack": "4.47.0", "webpack": "4.47.0",

View file

@ -1,13 +1,13 @@
{ {
"extends": [ "extends": [
"config:base" "config:base"
], ],
"baseBranches": [], "baseBranches": [],
"includeForks": true, "includeForks": true,
"automerge": false, "automerge": false,
"branchPrefix": "renovate-deps-update-", "branchPrefix": "renovate-deps-update-",
"rangeStrategy": "pin", "rangeStrategy": "pin",
"digest": { "digest": {
"enabled": false "enabled": false
} }
} }

File diff suppressed because one or more lines are too long

View file

@ -40,6 +40,7 @@ http {
gzip_types *; gzip_types *;
gzip_proxied any; gzip_proxied any;
gzip_comp_level 1; gzip_comp_level 1;
gzip_http_version 1.0;
gunzip on; gunzip on;
gzip_static on; gzip_static on;