Compare commits

..

No commits in common. "develop" and "v2.10.0" have entirely different histories.

316 changed files with 19062 additions and 13979 deletions

View File

@ -1,21 +0,0 @@
name: 'Close stale issues and PRs'
on:
schedule:
- cron: '30 1 * * *'
workflow_dispatch:
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
stale-issue-label: 'stale'
stale-pr-label: 'stale'
stale-issue-message: 'Issue is now considered stale. If you want to keep it open, please comment :+1:'
stale-pr-message: 'PR is now considered stale. If you want to keep it open, please comment :+1:'
close-issue-message: 'Issue was closed due to inactivity.'
close-pr-message: 'PR was closed due to inactivity.'
days-before-stale: 182
days-before-close: 365
operations-per-run: 50

4
.gitignore vendored
View File

@ -3,7 +3,3 @@
._* ._*
.vscode .vscode
certbot-help.txt certbot-help.txt
test/node_modules
*/node_modules
docker/dev/dnsrouter-config.json.tmp
docker/dev/resolv.conf

View File

@ -1 +1 @@
2.12.3 2.10.0

223
Jenkinsfile vendored
View File

@ -17,9 +17,13 @@ pipeline {
IMAGE = 'nginx-proxy-manager' IMAGE = 'nginx-proxy-manager'
BUILD_VERSION = getVersion() BUILD_VERSION = getVersion()
MAJOR_VERSION = '2' MAJOR_VERSION = '2'
BRANCH_LOWER = "${BRANCH_NAME.toLowerCase().replaceAll('\\\\', '-').replaceAll('/', '-').replaceAll('\\.', '-')}" BRANCH_LOWER = "${BRANCH_NAME.toLowerCase().replaceAll('/', '-')}"
BUILDX_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}" COMPOSE_PROJECT_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}"
COMPOSE_FILE = 'docker/docker-compose.ci.yml'
COMPOSE_INTERACTIVE_NO_CLI = 1 COMPOSE_INTERACTIVE_NO_CLI = 1
BUILDX_NAME = "${COMPOSE_PROJECT_NAME}"
DOCS_BUCKET = 'jc21-npm-site'
DOCS_CDN = 'EN1G6DEWZUTDT'
} }
stages { stages {
stage('Environment') { stage('Environment') {
@ -43,7 +47,7 @@ pipeline {
steps { steps {
script { script {
// Defaults to the Branch name, which is applies to all branches AND pr's // Defaults to the Branch name, which is applies to all branches AND pr's
buildxPushTags = "-t docker.io/nginxproxymanager/${IMAGE}-dev:${BRANCH_LOWER}" buildxPushTags = "-t docker.io/jc21/${IMAGE}:github-${BRANCH_LOWER}"
} }
} }
} }
@ -56,18 +60,9 @@ pipeline {
sh 'sed -i -E "s/(version-)[0-9]+\\.[0-9]+\\.[0-9]+(-green)/\\1${BUILD_VERSION}\\2/" README.md' sh 'sed -i -E "s/(version-)[0-9]+\\.[0-9]+\\.[0-9]+(-green)/\\1${BUILD_VERSION}\\2/" README.md'
} }
} }
stage('Docker Login') {
steps {
withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) {
sh 'docker login -u "${duser}" -p "${dpass}"'
} }
} }
} stage('Build and Test') {
}
}
stage('Builds') {
parallel {
stage('Project') {
steps { steps {
script { script {
// Frontend and Backend // Frontend and Backend
@ -91,118 +86,75 @@ pipeline {
} }
} }
} }
stage('Integration Tests Sqlite') {
steps {
// Bring up a stack
sh 'docker-compose up -d fullstack-sqlite'
sh './scripts/wait-healthy $(docker-compose ps -q fullstack-sqlite) 120'
// Run tests
sh 'rm -rf test/results'
sh 'docker-compose up cypress-sqlite'
// Get results
sh 'docker cp -L "$(docker-compose ps -q cypress-sqlite):/test/results" test/'
}
post {
always {
// Dumps to analyze later
sh 'mkdir -p debug'
sh 'docker-compose logs fullstack-sqlite > debug/docker_fullstack_sqlite.log'
sh 'docker-compose logs db > debug/docker_db.log'
// Cypress videos and screenshot artifacts
dir(path: 'test/results') {
archiveArtifacts allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml'
}
junit 'test/results/junit/*'
}
}
}
stage('Integration Tests Mysql') {
steps {
// Bring up a stack
sh 'docker-compose up -d fullstack-mysql'
sh './scripts/wait-healthy $(docker-compose ps -q fullstack-mysql) 120'
// Run tests
sh 'rm -rf test/results'
sh 'docker-compose up cypress-mysql'
// Get results
sh 'docker cp -L "$(docker-compose ps -q cypress-mysql):/test/results" test/'
}
post {
always {
// Dumps to analyze later
sh 'mkdir -p debug'
sh 'docker-compose logs fullstack-mysql > debug/docker_fullstack_mysql.log'
sh 'docker-compose logs db > debug/docker_db.log'
// Cypress videos and screenshot artifacts
dir(path: 'test/results') {
archiveArtifacts allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml'
}
junit 'test/results/junit/*'
}
}
}
stage('Docs') { stage('Docs') {
when {
not {
equals expected: 'UNSTABLE', actual: currentBuild.result
}
}
steps { steps {
dir(path: 'docs') { dir(path: 'docs') {
sh 'yarn install' sh 'yarn install'
sh 'yarn build' sh 'yarn build'
} }
}
}
}
}
stage('Test Sqlite') {
environment {
COMPOSE_PROJECT_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}_sqlite"
COMPOSE_FILE = 'docker/docker-compose.ci.yml:docker/docker-compose.ci.sqlite.yml'
}
when {
not {
equals expected: 'UNSTABLE', actual: currentBuild.result
}
}
steps {
sh 'rm -rf ./test/results/junit/*'
sh './scripts/ci/fulltest-cypress'
}
post {
always {
// Dumps to analyze later
sh 'mkdir -p debug/sqlite'
sh 'docker logs $(docker-compose ps --all -q fullstack) > debug/sqlite/docker_fullstack.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q stepca) > debug/sqlite/docker_stepca.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q pdns) > debug/sqlite/docker_pdns.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q pdns-db) > debug/sqlite/docker_pdns-db.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q dnsrouter) > debug/sqlite/docker_dnsrouter.log 2>&1'
junit 'test/results/junit/*'
sh 'docker-compose down --remove-orphans --volumes -t 30 || true'
}
unstable {
dir(path: 'test/results') {
archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml')
}
}
}
}
stage('Test Mysql') {
environment {
COMPOSE_PROJECT_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}_mysql"
COMPOSE_FILE = 'docker/docker-compose.ci.yml:docker/docker-compose.ci.mysql.yml'
}
when {
not {
equals expected: 'UNSTABLE', actual: currentBuild.result
}
}
steps {
sh 'rm -rf ./test/results/junit/*'
sh './scripts/ci/fulltest-cypress'
}
post {
always {
// Dumps to analyze later
sh 'mkdir -p debug/mysql'
sh 'docker logs $(docker-compose ps --all -q fullstack) > debug/mysql/docker_fullstack.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q stepca) > debug/mysql/docker_stepca.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q pdns) > debug/mysql/docker_pdns.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q pdns-db) > debug/mysql/docker_pdns-db.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q dnsrouter) > debug/mysql/docker_dnsrouter.log 2>&1'
junit 'test/results/junit/*'
sh 'docker-compose down --remove-orphans --volumes -t 30 || true'
}
unstable {
dir(path: 'test/results') {
archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml')
}
}
}
}
stage('Test Postgres') {
environment {
COMPOSE_PROJECT_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}_postgres"
COMPOSE_FILE = 'docker/docker-compose.ci.yml:docker/docker-compose.ci.postgres.yml'
}
when {
not {
equals expected: 'UNSTABLE', actual: currentBuild.result
}
}
steps {
sh 'rm -rf ./test/results/junit/*'
sh './scripts/ci/fulltest-cypress'
}
post {
always {
// Dumps to analyze later
sh 'mkdir -p debug/postgres'
sh 'docker logs $(docker-compose ps --all -q fullstack) > debug/postgres/docker_fullstack.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q stepca) > debug/postgres/docker_stepca.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q pdns) > debug/postgres/docker_pdns.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q pdns-db) > debug/postgres/docker_pdns-db.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q dnsrouter) > debug/postgres/docker_dnsrouter.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q db-postgres) > debug/postgres/docker_db-postgres.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q authentik) > debug/postgres/docker_authentik.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q authentik-redis) > debug/postgres/docker_authentik-redis.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q authentik-ldap) > debug/postgres/docker_authentik-ldap.log 2>&1'
junit 'test/results/junit/*' dir(path: 'docs/.vuepress/dist') {
sh 'docker-compose down --remove-orphans --volumes -t 30 || true' sh 'tar -czf ../../docs.tgz *'
}
unstable {
dir(path: 'test/results') {
archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml')
}
} }
archiveArtifacts(artifacts: 'docs/docs.tgz', allowEmptyArchive: false)
} }
} }
stage('MultiArch Build') { stage('MultiArch Build') {
@ -212,22 +164,23 @@ pipeline {
} }
} }
steps { steps {
withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) {
sh 'docker login -u "${duser}" -p "${dpass}"'
sh "./scripts/buildx --push ${buildxPushTags}" sh "./scripts/buildx --push ${buildxPushTags}"
} }
} }
stage('Docs / Comment') { }
parallel { stage('Docs Deploy') {
stage('Docs Job') {
when { when {
allOf { allOf {
branch pattern: "^(develop|master)\$", comparator: "REGEXP" branch 'master'
not { not {
equals expected: 'UNSTABLE', actual: currentBuild.result equals expected: 'UNSTABLE', actual: currentBuild.result
} }
} }
} }
steps { steps {
build wait: false, job: 'nginx-proxy-manager-docs', parameters: [string(name: 'docs_branch', value: "$BRANCH_NAME")] npmDocsRelease("$DOCS_BUCKET", "$DOCS_CDN")
} }
} }
stage('PR Comment') { stage('PR Comment') {
@ -241,30 +194,30 @@ pipeline {
} }
steps { steps {
script { script {
npmGithubPrComment("""Docker Image for build ${BUILD_NUMBER} is available on npmGithubPrComment("Docker Image for build ${BUILD_NUMBER} is available on [DockerHub](https://cloud.docker.com/repository/docker/jc21/${IMAGE}) as `jc21/${IMAGE}:github-${BRANCH_LOWER}`\n\n**Note:** ensure you backup your NPM instance before testing this PR image! Especially if this PR contains database changes.", true)
[DockerHub](https://cloud.docker.com/repository/docker/nginxproxymanager/${IMAGE}-dev)
as `nginxproxymanager/${IMAGE}-dev:${BRANCH_LOWER}`
**Note:** ensure you backup your NPM instance before testing this image! Especially if there are database changes
**Note:** this is a different docker image namespace than the official image
""", true)
}
}
} }
} }
} }
} }
post { post {
always { always {
sh 'docker-compose down --remove-orphans --volumes -t 30'
sh 'echo Reverting ownership' sh 'echo Reverting ownership'
sh 'docker run --rm -v "$(pwd):/data" jc21/ci-tools chown -R "$(id -u):$(id -g)" /data' sh 'docker run --rm -v $(pwd):/data jc21/ci-tools chown -R $(id -u):$(id -g) /data'
printResult(true) }
success {
juxtapose event: 'success'
sh 'figlet "SUCCESS"'
} }
failure { failure {
archiveArtifacts(artifacts: 'debug/**/*.*', allowEmptyArchive: true) archiveArtifacts(artifacts: 'debug/**.*', allowEmptyArchive: true)
juxtapose event: 'failure'
sh 'figlet "FAILURE"'
} }
unstable { unstable {
archiveArtifacts(artifacts: 'debug/**/*.*', allowEmptyArchive: true) archiveArtifacts(artifacts: 'debug/**.*', allowEmptyArchive: true)
juxtapose event: 'unstable'
sh 'figlet "UNSTABLE"'
} }
} }
} }

View File

@ -1,7 +1,7 @@
<p align="center"> <p align="center">
<img src="https://nginxproxymanager.com/github.png"> <img src="https://nginxproxymanager.com/github.png">
<br><br> <br><br>
<img src="https://img.shields.io/badge/version-2.12.3-green.svg?style=for-the-badge"> <img src="https://img.shields.io/badge/version-2.10.0-green.svg?style=for-the-badge">
<a href="https://hub.docker.com/repository/docker/jc21/nginx-proxy-manager"> <a href="https://hub.docker.com/repository/docker/jc21/nginx-proxy-manager">
<img src="https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge"> <img src="https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge">
</a> </a>
@ -19,7 +19,7 @@ running at home or otherwise, including free SSL, without having to know too muc
## Project Goal ## Project Goal
I created this project to fill a personal need to provide users with an easy way to accomplish reverse I created this project to fill a personal need to provide users with a easy way to accomplish reverse
proxying hosts with SSL termination and it had to be so easy that a monkey could do it. This goal hasn't changed. proxying hosts with SSL termination and it had to be so easy that a monkey could do it. This goal hasn't changed.
While there might be advanced options they are optional and the project should be as simple as possible While there might be advanced options they are optional and the project should be as simple as possible
so that the barrier for entry here is low. so that the barrier for entry here is low.
@ -56,9 +56,10 @@ I won't go in to too much detail here but here are the basics for someone new to
2. Create a docker-compose.yml file similar to this: 2. Create a docker-compose.yml file similar to this:
```yml ```yml
version: '3.8'
services: services:
app: app:
image: 'docker.io/jc21/nginx-proxy-manager:latest' image: 'jc21/nginx-proxy-manager:latest'
restart: unless-stopped restart: unless-stopped
ports: ports:
- '80:80' - '80:80'
@ -97,18 +98,7 @@ Password: changeme
Immediately after logging in with this default user you will be asked to modify your details and change your password. Immediately after logging in with this default user you will be asked to modify your details and change your password.
## Contributing ## Contributors
All are welcome to create pull requests for this project, against the `develop` branch. Official releases are created from the `master` branch.
CI is used in this project. All PR's must pass before being considered. After passing,
docker builds for PR's are available on dockerhub for manual verifications.
Documentation within the `develop` branch is available for preview at
[https://develop.nginxproxymanager.com](https://develop.nginxproxymanager.com)
### Contributors
Special thanks to [all of our contributors](https://github.com/NginxProxyManager/nginx-proxy-manager/graphs/contributors). Special thanks to [all of our contributors](https://github.com/NginxProxyManager/nginx-proxy-manager/graphs/contributors).
@ -117,4 +107,5 @@ Special thanks to [all of our contributors](https://github.com/NginxProxyManager
1. [Found a bug?](https://github.com/NginxProxyManager/nginx-proxy-manager/issues) 1. [Found a bug?](https://github.com/NginxProxyManager/nginx-proxy-manager/issues)
2. [Discussions](https://github.com/NginxProxyManager/nginx-proxy-manager/discussions) 2. [Discussions](https://github.com/NginxProxyManager/nginx-proxy-manager/discussions)
3. [Reddit](https://reddit.com/r/nginxproxymanager) 3. [Development Gitter](https://gitter.im/nginx-proxy-manager/community)
4. [Reddit](https://reddit.com/r/nginxproxymanager)

8
backend/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,8 @@
{
"editor.insertSpaces": false,
"editor.formatOnSave": true,
"files.trimTrailingWhitespace": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
}

View File

@ -52,7 +52,7 @@ app.use(function (req, res, next) {
}); });
app.use(require('./lib/express/jwt')()); app.use(require('./lib/express/jwt')());
app.use('/', require('./routes/main')); app.use('/', require('./routes/api/main'));
// production error handler // production error handler
// no stacktraces leaked to user // no stacktraces leaked to user

View File

@ -1,6 +1,6 @@
{ {
"database": { "database": {
"engine": "mysql2", "engine": "mysql",
"host": "db", "host": "db",
"name": "npm", "name": "npm",
"user": "npm", "user": "npm",

1254
backend/doc/api.swagger.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,23 @@
#!/usr/bin/env node #!/usr/bin/env node
const schema = require('./schema');
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 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(schema.getCompiledSchema) .then(() => {
return apiValidator.loadSchemas;
})
.then(internalIpRanges.fetch) .then(internalIpRanges.fetch)
.then(() => { .then(() => {
internalCertificate.initTimer(); internalCertificate.initTimer();
internalIpRanges.initTimer(); internalIpRanges.initTimer();
@ -31,7 +34,7 @@ async function appStart () {
}); });
}) })
.catch((err) => { .catch((err) => {
logger.error(err.message, err); logger.error(err.message);
setTimeout(appStart, 1000); setTimeout(appStart, 1000);
}); });
} }

View File

@ -81,7 +81,7 @@ const internalAccessList = {
return internalAccessList.build(row) return internalAccessList.build(row)
.then(() => { .then(() => {
if (parseInt(row.proxy_host_count, 10)) { if (row.proxy_host_count) {
return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts); return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
} }
}) })
@ -204,6 +204,7 @@ const internalAccessList = {
}); });
} }
}) })
.then(internalNginx.reload)
.then(() => { .then(() => {
// Add to audit log // Add to audit log
return internalAuditLog.add(access, { return internalAuditLog.add(access, {
@ -223,10 +224,10 @@ const internalAccessList = {
.then((row) => { .then((row) => {
return internalAccessList.build(row) return internalAccessList.build(row)
.then(() => { .then(() => {
if (parseInt(row.proxy_host_count, 10)) { 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(() => { .then(() => {
return internalAccessList.maskItems(row); return internalAccessList.maskItems(row);
}); });
@ -252,13 +253,9 @@ const internalAccessList = {
let query = accessListModel let query = accessListModel
.query() .query()
.select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count')) .select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count'))
.leftJoin('proxy_host', function() { .joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0')
this.on('proxy_host.access_list_id', '=', 'access_list.id')
.andOn('proxy_host.is_deleted', '=', 0);
})
.where('access_list.is_deleted', 0) .where('access_list.is_deleted', 0)
.andWhere('access_list.id', data.id) .andWhere('access_list.id', data.id)
.groupBy('access_list.id')
.allowGraph('[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]') .allowGraph('[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]')
.first(); .first();
@ -273,7 +270,7 @@ const internalAccessList = {
return query.then(utils.omitRow(omissions())); return query.then(utils.omitRow(omissions()));
}) })
.then((row) => { .then((row) => {
if (!row || !row.id) { if (!row) {
throw new error.ItemNotFoundError(data.id); throw new error.ItemNotFoundError(data.id);
} }
if (!skip_masking && typeof row.items !== 'undefined' && row.items) { if (!skip_masking && typeof row.items !== 'undefined' && row.items) {
@ -300,7 +297,7 @@ const internalAccessList = {
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 || !row.id) { if (!row) {
throw new error.ItemNotFoundError(data.id); throw new error.ItemNotFoundError(data.id);
} }
@ -377,10 +374,7 @@ const internalAccessList = {
let query = accessListModel let query = accessListModel
.query() .query()
.select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count')) .select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count'))
.leftJoin('proxy_host', function() { .joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0')
this.on('proxy_host.access_list_id', '=', 'access_list.id')
.andOn('proxy_host.is_deleted', '=', 0);
})
.where('access_list.is_deleted', 0) .where('access_list.is_deleted', 0)
.groupBy('access_list.id') .groupBy('access_list.id')
.allowGraph('[owner,items,clients]') .allowGraph('[owner,items,clients]')
@ -508,13 +502,8 @@ const internalAccessList = {
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('openssl', ['passwd', '-apr1', item.password]) utils.execFile('/usr/bin/htpasswd', ['-b', htpasswd_file, item.username, item.password])
.then((res) => { .then((/*result*/) => {
try {
fs.appendFileSync(htpasswd_file, item.username + ':' + res + '\n', {encoding: 'utf8'});
} catch (err) {
reject(err);
}
next(); next();
}) })
.catch((err) => { .catch((err) => {

View File

@ -1,6 +1,5 @@
const error = require('../lib/error'); const error = require('../lib/error');
const auditLogModel = require('../models/audit-log'); const auditLogModel = require('../models/audit-log');
const {castJsonIfNeed} = require('../lib/helpers');
const internalAuditLog = { const internalAuditLog = {
@ -23,9 +22,9 @@ const internalAuditLog = {
.allowGraph('[user]'); .allowGraph('[user]');
// Query is used for searching // Query is used for searching
if (typeof search_query === 'string' && search_query.length > 0) { if (typeof search_query === 'string') {
query.where(function () { query.where(function () {
this.where(castJsonIfNeed('meta'), 'like', '%' + search_query + '%'); this.where('meta', 'like', '%' + search_query + '%');
}); });
} }

View File

@ -3,29 +3,25 @@ const fs = require('fs');
const https = require('https'); const https = require('https');
const tempWrite = require('temp-write'); const tempWrite = require('temp-write');
const moment = require('moment'); const moment = require('moment');
const archiver = require('archiver');
const path = require('path');
const { isArray } = require('lodash');
const logger = require('../logger').ssl; const logger = require('../logger').ssl;
const config = require('../lib/config'); const config = require('../lib/config');
const error = require('../lib/error'); const error = require('../lib/error');
const utils = require('../lib/utils'); const utils = require('../lib/utils');
const certbot = require('../lib/certbot');
const certificateModel = require('../models/certificate'); const certificateModel = require('../models/certificate');
const tokenModel = require('../models/token'); const dnsPlugins = require('../global/certbot-dns-plugins');
const dnsPlugins = require('../global/certbot-dns-plugins.json');
const internalAuditLog = require('./audit-log'); const internalAuditLog = require('./audit-log');
const internalNginx = require('./nginx'); const internalNginx = require('./nginx');
const internalHost = require('./host'); const internalHost = require('./host');
const archiver = require('archiver');
const path = require('path');
const { isArray } = require('lodash');
const letsencryptStaging = config.useLetsencryptStaging(); const letsencryptStaging = config.useLetsencryptStaging();
const letsencryptServer = config.useLetsencryptServer();
const letsencryptConfig = '/etc/letsencrypt.ini'; const letsencryptConfig = '/etc/letsencrypt.ini';
const certbotCommand = 'certbot'; const certbotCommand = 'certbot';
function omissions() { function omissions() {
return ['is_deleted', 'owner.is_deleted']; return ['is_deleted'];
} }
const internalCertificate = { const internalCertificate = {
@ -34,7 +30,6 @@ const internalCertificate = {
intervalTimeout: 1000 * 60 * 60, // 1 hour intervalTimeout: 1000 * 60 * 60, // 1 hour
interval: null, interval: null,
intervalProcessing: false, intervalProcessing: false,
renewBeforeExpirationBy: [30, 'days'],
initTimer: () => { initTimer: () => {
logger.info('Let\'s Encrypt Renewal Timer initialized'); logger.info('Let\'s Encrypt Renewal Timer initialized');
@ -49,51 +44,62 @@ const internalCertificate = {
processExpiringHosts: () => { processExpiringHosts: () => {
if (!internalCertificate.intervalProcessing) { if (!internalCertificate.intervalProcessing) {
internalCertificate.intervalProcessing = true; internalCertificate.intervalProcessing = true;
logger.info('Renewing SSL certs expiring within ' + internalCertificate.renewBeforeExpirationBy[0] + ' ' + internalCertificate.renewBeforeExpirationBy[1] + ' ...'); logger.info('Renewing SSL certs close to expiry...');
const expirationThreshold = moment().add(internalCertificate.renewBeforeExpirationBy[0], internalCertificate.renewBeforeExpirationBy[1]).format('YYYY-MM-DD HH:mm:ss'); const cmd = certbotCommand + ' renew --non-interactive --quiet ' +
'--config "' + letsencryptConfig + '" ' +
'--work-dir "/tmp/letsencrypt-lib" ' +
'--logs-dir "/tmp/letsencrypt-log" ' +
'--preferred-challenges "dns,http" ' +
'--disable-hook-validation ' +
(letsencryptStaging ? '--staging' : '');
// Fetch all the letsencrypt certs from the db that will expire within the configured threshold return utils.exec(cmd)
certificateModel .then((result) => {
if (result) {
logger.info('Renew Result: ' + result);
}
return internalNginx.reload()
.then(() => {
logger.info('Renew Complete');
return result;
});
})
.then(() => {
// Now go and fetch all the letsencrypt certs from the db and query the files and update expiry times
return certificateModel
.query() .query()
.where('is_deleted', 0) .where('is_deleted', 0)
.andWhere('provider', 'letsencrypt') .andWhere('provider', 'letsencrypt')
.andWhere('expires_on', '<', expirationThreshold)
.then((certificates) => { .then((certificates) => {
if (!certificates || !certificates.length) { if (certificates && certificates.length) {
return null; let promises = [];
}
/** certificates.map(function (certificate) {
* Renews must be run sequentially or we'll get an error 'Another promises.push(
* instance of Certbot is already running.' internalCertificate.getCertificateInfoFromFile('/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem')
*/ .then((cert_info) => {
let sequence = Promise.resolve(); return certificateModel
.query()
certificates.forEach(function (certificate) { .where('id', certificate.id)
sequence = sequence.then(() => .andWhere('provider', 'letsencrypt')
internalCertificate .patch({
.renew( expires_on: moment(cert_info.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss')
{ });
can: () => })
Promise.resolve({
permission_visibility: 'all',
}),
token: new tokenModel(),
},
{ id: certificate.id },
)
.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);
}), })
); );
}); });
return sequence; return Promise.all(promises);
}
});
}) })
.then(() => { .then(() => {
logger.info('Completed SSL cert renew process');
internalCertificate.intervalProcessing = false; internalCertificate.intervalProcessing = false;
}) })
.catch((err) => { .catch((err) => {
@ -209,7 +215,6 @@ const internalCertificate = {
.patchAndFetchById(certificate.id, { .patchAndFetchById(certificate.id, {
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')
}) })
.then(utils.omitRow(omissions()))
.then((saved_row) => { .then((saved_row) => {
// Add cert data for audit log // Add cert data for audit log
saved_row.meta = _.assign({}, saved_row.meta, { saved_row.meta = _.assign({}, saved_row.meta, {
@ -313,9 +318,6 @@ const internalCertificate = {
.where('is_deleted', 0) .where('is_deleted', 0)
.andWhere('id', data.id) .andWhere('id', data.id)
.allowGraph('[owner]') .allowGraph('[owner]')
.allowGraph('[proxy_hosts]')
.allowGraph('[redirection_hosts]')
.allowGraph('[dead_hosts]')
.first(); .first();
if (access_data.permission_visibility !== 'all') { if (access_data.permission_visibility !== 'all') {
@ -329,7 +331,7 @@ const internalCertificate = {
return query.then(utils.omitRow(omissions())); return query.then(utils.omitRow(omissions()));
}) })
.then((row) => { .then((row) => {
if (!row || !row.id) { if (!row) {
throw new error.ItemNotFoundError(data.id); throw new error.ItemNotFoundError(data.id);
} }
// Custom omissions // Custom omissions
@ -418,7 +420,7 @@ const internalCertificate = {
return internalCertificate.get(access, {id: data.id}); return internalCertificate.get(access, {id: data.id});
}) })
.then((row) => { .then((row) => {
if (!row || !row.id) { if (!row) {
throw new error.ItemNotFoundError(data.id); throw new error.ItemNotFoundError(data.id);
} }
@ -467,9 +469,6 @@ const internalCertificate = {
.where('is_deleted', 0) .where('is_deleted', 0)
.groupBy('id') .groupBy('id')
.allowGraph('[owner]') .allowGraph('[owner]')
.allowGraph('[proxy_hosts]')
.allowGraph('[redirection_hosts]')
.allowGraph('[dead_hosts]')
.orderBy('nice_name', 'ASC'); .orderBy('nice_name', 'ASC');
if (access_data.permission_visibility !== 'all') { if (access_data.permission_visibility !== 'all') {
@ -739,29 +738,29 @@ const internalCertificate = {
return utils.exec('openssl x509 -in ' + certificate_file + ' -subject -noout') return utils.exec('openssl x509 -in ' + certificate_file + ' -subject -noout')
.then((result) => { .then((result) => {
// Examples:
// subject=CN = *.jc21.com
// subject=CN = something.example.com // subject=CN = something.example.com
const regex = /(?:subject=)?[^=]+=\s+(\S+)/gim; const regex = /(?:subject=)?[^=]+=\s+(\S+)/gim;
const match = regex.exec(result); const match = regex.exec(result);
if (match && typeof match[1] !== 'undefined') {
certData['cn'] = match[1]; if (typeof match[1] === 'undefined') {
throw new error.ValidationError('Could not determine subject from certificate: ' + result);
} }
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');
}) })
.then((result) => { .then((result) => {
// Examples:
// issuer=C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3 // issuer=C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
// issuer=C = US, O = Let's Encrypt, CN = E5
// issuer=O = NginxProxyManager, CN = NginxProxyManager Intermediate CA","O = NginxProxyManager, CN = NginxProxyManager Intermediate CA
const regex = /^(?:issuer=)?(.*)$/gim; const regex = /^(?:issuer=)?(.*)$/gim;
const match = regex.exec(result); const match = regex.exec(result);
if (match && typeof match[1] !== 'undefined') {
certData['issuer'] = match[1]; if (typeof match[1] === 'undefined') {
throw new error.ValidationError('Could not determine issuer from certificate: ' + result);
} }
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');
@ -836,18 +835,17 @@ const internalCertificate = {
requestLetsEncryptSsl: (certificate) => { requestLetsEncryptSsl: (certificate) => {
logger.info('Requesting Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', ')); logger.info('Requesting Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
const cmd = `${certbotCommand} certonly ` + const cmd = certbotCommand + ' certonly ' +
`--config '${letsencryptConfig}' ` + '--config "' + letsencryptConfig + '" ' +
'--work-dir "/tmp/letsencrypt-lib" ' + '--work-dir "/tmp/letsencrypt-lib" ' +
'--logs-dir "/tmp/letsencrypt-log" ' + '--logs-dir "/tmp/letsencrypt-log" ' +
`--cert-name "npm-${certificate.id}" ` + '--cert-name "npm-' + certificate.id + '" ' +
'--agree-tos ' + '--agree-tos ' +
'--authenticator webroot ' + '--authenticator webroot ' +
`--email '${certificate.meta.letsencrypt_email}' ` + '--email "' + certificate.meta.letsencrypt_email + '" ' +
'--preferred-challenges "dns,http" ' + '--preferred-challenges "dns,http" ' +
`--domains "${certificate.domain_names.join(',')}" ` + '--domains "' + certificate.domain_names.join(',') + '" ' +
(letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') + (letsencryptStaging ? '--staging' : '');
(letsencryptStaging && letsencryptServer === null ? '--staging ' : '');
logger.info('Command:', cmd); logger.info('Command:', cmd);
@ -860,65 +858,74 @@ const internalCertificate = {
/** /**
* @param {Object} certificate the certificate row * @param {Object} certificate the certificate row
* @param {String} dns_provider the dns provider name (key used in `certbot-dns-plugins.json`) * @param {String} dns_provider the dns provider name (key used in `certbot-dns-plugins.js`)
* @param {String | null} credentials the content of this providers credentials file * @param {String | null} credentials the content of this providers credentials file
* @param {String} propagation_seconds * @param {String} propagation_seconds the cloudflare api token
* @returns {Promise} * @returns {Promise}
*/ */
requestLetsEncryptSslWithDnsChallenge: async (certificate) => { requestLetsEncryptSslWithDnsChallenge: (certificate) => {
await certbot.installPlugin(certificate.meta.dns_provider); const dns_plugin = dnsPlugins[certificate.meta.dns_provider];
const dnsPlugin = dnsPlugins[certificate.meta.dns_provider];
logger.info(`Requesting Let'sEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); if (!dns_plugin) {
throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`);
}
logger.info(`Requesting Let'sEncrypt certificates via ${dns_plugin.display_name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);
const credentialsLocation = '/etc/letsencrypt/credentials/credentials-' + certificate.id; const credentialsLocation = '/etc/letsencrypt/credentials/credentials-' + certificate.id;
fs.mkdirSync('/etc/letsencrypt/credentials', { recursive: true }); // Escape single quotes and backslashes
fs.writeFileSync(credentialsLocation, certificate.meta.dns_provider_credentials, {mode: 0o600}); const escapedCredentials = certificate.meta.dns_provider_credentials.replaceAll('\'', '\\\'').replaceAll('\\', '\\\\');
const credentialsCmd = 'mkdir -p /etc/letsencrypt/credentials 2> /dev/null; echo \'' + escapedCredentials + '\' > \'' + credentialsLocation + '\' && chmod 600 \'' + credentialsLocation + '\'';
// we call `. /opt/certbot/bin/activate` (`.` is alternative to `source` in dash) to access certbot venv
const prepareCmd = '. /opt/certbot/bin/activate && pip install --no-cache-dir --user ' + dns_plugin.package_name + (dns_plugin.version_requirement || '') + ' ' + dns_plugin.dependencies + ' && deactivate';
// Whether the plugin has a --<name>-credentials argument // Whether the plugin has a --<name>-credentials argument
const hasConfigArg = certificate.meta.dns_provider !== 'route53'; const hasConfigArg = certificate.meta.dns_provider !== 'route53';
let mainCmd = certbotCommand + ' certonly ' + let mainCmd = certbotCommand + ' certonly ' +
`--config '${letsencryptConfig}' ` + '--config "' + letsencryptConfig + '" ' +
'--work-dir "/tmp/letsencrypt-lib" ' + '--work-dir "/tmp/letsencrypt-lib" ' +
'--logs-dir "/tmp/letsencrypt-log" ' + '--logs-dir "/tmp/letsencrypt-log" ' +
`--cert-name 'npm-${certificate.id}' ` + '--cert-name "npm-' + certificate.id + '" ' +
'--agree-tos ' + '--agree-tos ' +
`--email '${certificate.meta.letsencrypt_email}' ` + '--email "' + certificate.meta.letsencrypt_email + '" ' +
`--domains '${certificate.domain_names.join(',')}' ` + '--domains "' + certificate.domain_names.join(',') + '" ' +
`--authenticator '${dnsPlugin.full_plugin_name}' ` + '--authenticator ' + dns_plugin.full_plugin_name + ' ' +
( (
hasConfigArg hasConfigArg
? `--${dnsPlugin.full_plugin_name}-credentials '${credentialsLocation}' ` ? '--' + dns_plugin.full_plugin_name + '-credentials "' + credentialsLocation + '"'
: '' : ''
) + ) +
( (
certificate.meta.propagation_seconds !== undefined certificate.meta.propagation_seconds !== undefined
? `--${dnsPlugin.full_plugin_name}-propagation-seconds '${certificate.meta.propagation_seconds}' ` ? ' --' + dns_plugin.full_plugin_name + '-propagation-seconds ' + certificate.meta.propagation_seconds
: '' : ''
) + ) +
(letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') + (letsencryptStaging ? ' --staging' : '');
(letsencryptStaging && letsencryptServer === null ? '--staging ' : '');
// Prepend the path to the credentials file as an environment variable // Prepend the path to the credentials file as an environment variable
if (certificate.meta.dns_provider === 'route53') { if (certificate.meta.dns_provider === 'route53') {
mainCmd = 'AWS_CONFIG_FILE=\'' + credentialsLocation + '\' ' + mainCmd; mainCmd = 'AWS_CONFIG_FILE=\'' + credentialsLocation + '\' ' + mainCmd;
} }
if (certificate.meta.dns_provider === 'duckdns') { logger.info('Command:', `${credentialsCmd} && ${prepareCmd} && ${mainCmd}`);
mainCmd = mainCmd + ' --dns-duckdns-no-txt-restore';
}
logger.info('Command:', mainCmd); return utils.exec(credentialsCmd)
.then(() => {
try { return utils.exec(prepareCmd)
const result = await utils.exec(mainCmd); .then(() => {
return utils.exec(mainCmd)
.then(async (result) => {
logger.info(result); logger.info(result);
return result; return result;
} catch (err) { });
// Don't fail if file does not exist, so no need for action in the callback });
fs.unlink(credentialsLocation, () => {}); }).catch(async (err) => {
// Don't fail if file does not exist
const delete_credentialsCmd = `rm -f '${credentialsLocation}' || true`;
await utils.exec(delete_credentialsCmd);
throw err; throw err;
} });
}, },
@ -974,15 +981,14 @@ const internalCertificate = {
logger.info('Renewing Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', ')); logger.info('Renewing Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
const cmd = certbotCommand + ' renew --force-renewal ' + const cmd = certbotCommand + ' renew --force-renewal ' +
`--config '${letsencryptConfig}' ` + '--config "' + letsencryptConfig + '" ' +
'--work-dir "/tmp/letsencrypt-lib" ' + '--work-dir "/tmp/letsencrypt-lib" ' +
'--logs-dir "/tmp/letsencrypt-log" ' + '--logs-dir "/tmp/letsencrypt-log" ' +
`--cert-name 'npm-${certificate.id}' ` + '--cert-name "npm-' + certificate.id + '" ' +
'--preferred-challenges "dns,http" ' + '--preferred-challenges "dns,http" ' +
'--no-random-sleep-on-renew ' + '--no-random-sleep-on-renew ' +
'--disable-hook-validation ' + '--disable-hook-validation ' +
(letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') + (letsencryptStaging ? '--staging' : '');
(letsencryptStaging && letsencryptServer === null ? '--staging ' : '');
logger.info('Command:', cmd); logger.info('Command:', cmd);
@ -998,23 +1004,22 @@ const internalCertificate = {
* @returns {Promise} * @returns {Promise}
*/ */
renewLetsEncryptSslWithDnsChallenge: (certificate) => { renewLetsEncryptSslWithDnsChallenge: (certificate) => {
const dnsPlugin = dnsPlugins[certificate.meta.dns_provider]; const dns_plugin = dnsPlugins[certificate.meta.dns_provider];
if (!dnsPlugin) { if (!dns_plugin) {
throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`); throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`);
} }
logger.info(`Renewing Let'sEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); logger.info(`Renewing Let'sEncrypt certificates via ${dns_plugin.display_name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);
let mainCmd = certbotCommand + ' renew --force-renewal ' + let mainCmd = certbotCommand + ' renew ' +
`--config "${letsencryptConfig}" ` + '--config "' + letsencryptConfig + '" ' +
'--work-dir "/tmp/letsencrypt-lib" ' + '--work-dir "/tmp/letsencrypt-lib" ' +
'--logs-dir "/tmp/letsencrypt-log" ' + '--logs-dir "/tmp/letsencrypt-log" ' +
`--cert-name 'npm-${certificate.id}' ` + '--cert-name "npm-' + certificate.id + '" ' +
'--disable-hook-validation ' + '--disable-hook-validation ' +
'--no-random-sleep-on-renew ' + '--no-random-sleep-on-renew ' +
(letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') + (letsencryptStaging ? ' --staging' : '');
(letsencryptStaging && letsencryptServer === null ? '--staging ' : '');
// Prepend the path to the credentials file as an environment variable // Prepend the path to the credentials file as an environment variable
if (certificate.meta.dns_provider === 'route53') { if (certificate.meta.dns_provider === 'route53') {
@ -1040,13 +1045,10 @@ const internalCertificate = {
logger.info('Revoking Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', ')); logger.info('Revoking Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
const mainCmd = certbotCommand + ' revoke ' + const mainCmd = certbotCommand + ' revoke ' +
`--config '${letsencryptConfig}' ` + '--config "' + letsencryptConfig + '" ' +
'--work-dir "/tmp/letsencrypt-lib" ' + '--cert-path "/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem" ' +
'--logs-dir "/tmp/letsencrypt-log" ' +
`--cert-path '/etc/letsencrypt/live/npm-${certificate.id}/fullchain.pem' ` +
'--delete-after-revoke ' + '--delete-after-revoke ' +
(letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') + (letsencryptStaging ? '--staging' : '');
(letsencryptStaging && letsencryptServer === null ? '--staging ' : '');
// Don't fail command if file does not exist // Don't fail command if file does not exist
const delete_credentialsCmd = `rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`; const delete_credentialsCmd = `rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`;
@ -1161,7 +1163,6 @@ const internalCertificate = {
const options = { const options = {
method: 'POST', method: 'POST',
headers: { headers: {
'User-Agent': 'Mozilla/5.0',
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(formBody) 'Content-Length': Buffer.byteLength(formBody)
} }
@ -1174,22 +1175,12 @@ const internalCertificate = {
res.on('data', (chunk) => responseBody = responseBody + chunk); res.on('data', (chunk) => responseBody = responseBody + chunk);
res.on('end', function () { res.on('end', function () {
try {
const parsedBody = JSON.parse(responseBody + ''); const parsedBody = JSON.parse(responseBody + '');
if (res.statusCode !== 200) { if (res.statusCode !== 200) {
logger.warn(`Failed to test HTTP challenge for domain ${domain} because HTTP status code ${res.statusCode} was returned: ${parsedBody.message}`); logger.warn(`Failed to test HTTP challenge for domain ${domain}`, res);
resolve(undefined); resolve(undefined);
} else { }
resolve(parsedBody); resolve(parsedBody);
}
} catch (err) {
if (res.statusCode !== 200) {
logger.warn(`Failed to test HTTP challenge for domain ${domain} because HTTP status code ${res.statusCode} was returned`);
} else {
logger.warn(`Failed to test HTTP challenge for domain ${domain} because response failed to be parsed: ${err.message}`);
}
resolve(undefined);
}
}); });
}); });
@ -1203,9 +1194,6 @@ const internalCertificate = {
if (!result) { if (!result) {
// Some error occurred while trying to get the data // Some error occurred while trying to get the data
return 'failed'; return 'failed';
} else if (result.error) {
logger.info(`HTTP challenge test failed for domain ${domain} because error was returned: ${result.error.msg}`);
return `other:${result.error.msg}`;
} else if (`${result.responsecode}` === '200' && result.htmlresponse === 'Success') { } else if (`${result.responsecode}` === '200' && result.htmlresponse === 'Success') {
// Server exists and has responded with the correct data // Server exists and has responded with the correct data
return 'ok'; return 'ok';

View File

@ -6,7 +6,6 @@ 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');
const {castJsonIfNeed} = require('../lib/helpers');
function omissions () { function omissions () {
return ['is_deleted']; return ['is_deleted'];
@ -49,12 +48,6 @@ const internalDeadHost = {
data.owner_user_id = access.token.getUserId(1); data.owner_user_id = access.token.getUserId(1);
data = internalHost.cleanSslHstsData(data); data = internalHost.cleanSslHstsData(data);
// Fix for db field not having a default value
// for this optional field.
if (typeof data.advanced_config === 'undefined') {
data.advanced_config = '';
}
return deadHostModel return deadHostModel
.query() .query()
.insertAndFetch(data) .insertAndFetch(data)
@ -240,7 +233,7 @@ const internalDeadHost = {
return query.then(utils.omitRow(omissions())); return query.then(utils.omitRow(omissions()));
}) })
.then((row) => { .then((row) => {
if (!row || !row.id) { if (!row) {
throw new error.ItemNotFoundError(data.id); throw new error.ItemNotFoundError(data.id);
} }
// Custom omissions // Custom omissions
@ -264,7 +257,7 @@ const internalDeadHost = {
return internalDeadHost.get(access, {id: data.id}); return internalDeadHost.get(access, {id: data.id});
}) })
.then((row) => { .then((row) => {
if (!row || !row.id) { if (!row) {
throw new error.ItemNotFoundError(data.id); throw new error.ItemNotFoundError(data.id);
} }
@ -312,7 +305,7 @@ const internalDeadHost = {
}); });
}) })
.then((row) => { .then((row) => {
if (!row || !row.id) { if (!row) {
throw new error.ItemNotFoundError(data.id); throw new error.ItemNotFoundError(data.id);
} else if (row.enabled) { } else if (row.enabled) {
throw new error.ValidationError('Host is already enabled'); throw new error.ValidationError('Host is already enabled');
@ -358,7 +351,7 @@ const internalDeadHost = {
return internalDeadHost.get(access, {id: data.id}); return internalDeadHost.get(access, {id: data.id});
}) })
.then((row) => { .then((row) => {
if (!row || !row.id) { if (!row) {
throw new error.ItemNotFoundError(data.id); throw new error.ItemNotFoundError(data.id);
} else if (!row.enabled) { } else if (!row.enabled) {
throw new error.ValidationError('Host is already disabled'); throw new error.ValidationError('Host is already disabled');
@ -410,16 +403,16 @@ const internalDeadHost = {
.where('is_deleted', 0) .where('is_deleted', 0)
.groupBy('id') .groupBy('id')
.allowGraph('[owner,certificate]') .allowGraph('[owner,certificate]')
.orderBy(castJsonIfNeed('domain_names'), 'ASC'); .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));
} }
// Query is used for searching // Query is used for searching
if (typeof search_query === 'string' && search_query.length > 0) { if (typeof search_query === 'string') {
query.where(function () { query.where(function () {
this.where(castJsonIfNeed('domain_names'), 'like', '%' + search_query + '%'); this.where('domain_names', 'like', '%' + search_query + '%');
}); });
} }

View File

@ -2,7 +2,6 @@ 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 {castJsonIfNeed} = require('../lib/helpers');
const internalHost = { const internalHost = {
@ -18,7 +17,7 @@ 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;
const combined_data = _.assign({}, existing_data, data); let 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;
@ -74,7 +73,7 @@ const internalHost = {
* @returns {Promise} * @returns {Promise}
*/ */
getHostsWithDomains: function (domain_names) { getHostsWithDomains: function (domain_names) {
const promises = [ let promises = [
proxyHostModel proxyHostModel
.query() .query()
.where('is_deleted', 0), .where('is_deleted', 0),
@ -126,19 +125,19 @@ const internalHost = {
* @returns {Promise} * @returns {Promise}
*/ */
isHostnameTaken: function (hostname, ignore_type, ignore_id) { isHostnameTaken: function (hostname, ignore_type, ignore_id) {
const promises = [ let promises = [
proxyHostModel proxyHostModel
.query() .query()
.where('is_deleted', 0) .where('is_deleted', 0)
.andWhere(castJsonIfNeed('domain_names'), 'like', '%' + hostname + '%'), .andWhere('domain_names', 'like', '%' + hostname + '%'),
redirectionHostModel redirectionHostModel
.query() .query()
.where('is_deleted', 0) .where('is_deleted', 0)
.andWhere(castJsonIfNeed('domain_names'), 'like', '%' + hostname + '%'), .andWhere('domain_names', 'like', '%' + hostname + '%'),
deadHostModel deadHostModel
.query() .query()
.where('is_deleted', 0) .where('is_deleted', 0)
.andWhere(castJsonIfNeed('domain_names'), 'like', '%' + hostname + '%') .andWhere('domain_names', 'like', '%' + hostname + '%')
]; ];
return Promise.all(promises) return Promise.all(promises)

View File

@ -181,9 +181,7 @@ const internalNginx = {
* @param {Object} host * @param {Object} host
* @returns {Promise} * @returns {Promise}
*/ */
generateConfig: (host_type, host_row) => { generateConfig: (host_type, host) => {
// Prevent modifying the original object:
let host = JSON.parse(JSON.stringify(host_row));
const nice_host_type = internalNginx.getFileFriendlyHostType(host_type); const nice_host_type = internalNginx.getFileFriendlyHostType(host_type);
if (config.debug()) { if (config.debug()) {

View File

@ -6,10 +6,9 @@ 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');
const {castJsonIfNeed} = require('../lib/helpers');
function omissions () { function omissions () {
return ['is_deleted', 'owner.is_deleted']; return ['is_deleted'];
} }
const internalProxyHost = { const internalProxyHost = {
@ -49,12 +48,6 @@ const internalProxyHost = {
data.owner_user_id = access.token.getUserId(1); data.owner_user_id = access.token.getUserId(1);
data = internalHost.cleanSslHstsData(data); data = internalHost.cleanSslHstsData(data);
// Fix for db field not having a default value
// for this optional field.
if (typeof data.advanced_config === 'undefined') {
data.advanced_config = '';
}
return proxyHostModel return proxyHostModel
.query() .query()
.insertAndFetch(data) .insertAndFetch(data)
@ -232,7 +225,7 @@ const internalProxyHost = {
.query() .query()
.where('is_deleted', 0) .where('is_deleted', 0)
.andWhere('id', data.id) .andWhere('id', data.id)
.allowGraph('[owner,access_list.[clients,items],certificate]') .allowGraph('[owner,access_list,access_list.[clients,items],certificate]')
.first(); .first();
if (access_data.permission_visibility !== 'all') { if (access_data.permission_visibility !== 'all') {
@ -246,7 +239,7 @@ const internalProxyHost = {
return query.then(utils.omitRow(omissions())); return query.then(utils.omitRow(omissions()));
}) })
.then((row) => { .then((row) => {
if (!row || !row.id) { if (!row) {
throw new error.ItemNotFoundError(data.id); throw new error.ItemNotFoundError(data.id);
} }
row = internalHost.cleanRowCertificateMeta(row); row = internalHost.cleanRowCertificateMeta(row);
@ -271,7 +264,7 @@ const internalProxyHost = {
return internalProxyHost.get(access, {id: data.id}); return internalProxyHost.get(access, {id: data.id});
}) })
.then((row) => { .then((row) => {
if (!row || !row.id) { if (!row) {
throw new error.ItemNotFoundError(data.id); throw new error.ItemNotFoundError(data.id);
} }
@ -319,7 +312,7 @@ const internalProxyHost = {
}); });
}) })
.then((row) => { .then((row) => {
if (!row || !row.id) { if (!row) {
throw new error.ItemNotFoundError(data.id); throw new error.ItemNotFoundError(data.id);
} else if (row.enabled) { } else if (row.enabled) {
throw new error.ValidationError('Host is already enabled'); throw new error.ValidationError('Host is already enabled');
@ -365,7 +358,7 @@ const internalProxyHost = {
return internalProxyHost.get(access, {id: data.id}); return internalProxyHost.get(access, {id: data.id});
}) })
.then((row) => { .then((row) => {
if (!row || !row.id) { if (!row) {
throw new error.ItemNotFoundError(data.id); throw new error.ItemNotFoundError(data.id);
} else if (!row.enabled) { } else if (!row.enabled) {
throw new error.ValidationError('Host is already disabled'); throw new error.ValidationError('Host is already disabled');
@ -417,16 +410,16 @@ const internalProxyHost = {
.where('is_deleted', 0) .where('is_deleted', 0)
.groupBy('id') .groupBy('id')
.allowGraph('[owner,access_list,certificate]') .allowGraph('[owner,access_list,certificate]')
.orderBy(castJsonIfNeed('domain_names'), 'ASC'); .orderBy('domain_names', 'ASC');
if (access_data.permission_visibility !== 'all') { if (access_data.permission_visibility !== 'all') {
query.andWhere('owner_user_id', access.token.getUserId(1)); query.andWhere('owner_user_id', access.token.getUserId(1));
} }
// Query is used for searching // Query is used for searching
if (typeof search_query === 'string' && search_query.length > 0) { if (typeof search_query === 'string') {
query.where(function () { query.where(function () {
this.where(castJsonIfNeed('domain_names'), 'like', `%${search_query}%`); this.where('domain_names', 'like', '%' + search_query + '%');
}); });
} }

View File

@ -6,7 +6,6 @@ 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');
const {castJsonIfNeed} = require('../lib/helpers');
function omissions () { function omissions () {
return ['is_deleted']; return ['is_deleted'];
@ -49,12 +48,6 @@ const internalRedirectionHost = {
data.owner_user_id = access.token.getUserId(1); data.owner_user_id = access.token.getUserId(1);
data = internalHost.cleanSslHstsData(data); data = internalHost.cleanSslHstsData(data);
// Fix for db field not having a default value
// for this optional field.
if (typeof data.advanced_config === 'undefined') {
data.advanced_config = '';
}
return redirectionHostModel return redirectionHostModel
.query() .query()
.insertAndFetch(data) .insertAndFetch(data)
@ -239,7 +232,7 @@ const internalRedirectionHost = {
return query.then(utils.omitRow(omissions())); return query.then(utils.omitRow(omissions()));
}) })
.then((row) => { .then((row) => {
if (!row || !row.id) { if (!row) {
throw new error.ItemNotFoundError(data.id); throw new error.ItemNotFoundError(data.id);
} }
row = internalHost.cleanRowCertificateMeta(row); row = internalHost.cleanRowCertificateMeta(row);
@ -264,7 +257,7 @@ const internalRedirectionHost = {
return internalRedirectionHost.get(access, {id: data.id}); return internalRedirectionHost.get(access, {id: data.id});
}) })
.then((row) => { .then((row) => {
if (!row || !row.id) { if (!row) {
throw new error.ItemNotFoundError(data.id); throw new error.ItemNotFoundError(data.id);
} }
@ -312,7 +305,7 @@ const internalRedirectionHost = {
}); });
}) })
.then((row) => { .then((row) => {
if (!row || !row.id) { if (!row) {
throw new error.ItemNotFoundError(data.id); throw new error.ItemNotFoundError(data.id);
} else if (row.enabled) { } else if (row.enabled) {
throw new error.ValidationError('Host is already enabled'); throw new error.ValidationError('Host is already enabled');
@ -358,7 +351,7 @@ const internalRedirectionHost = {
return internalRedirectionHost.get(access, {id: data.id}); return internalRedirectionHost.get(access, {id: data.id});
}) })
.then((row) => { .then((row) => {
if (!row || !row.id) { if (!row) {
throw new error.ItemNotFoundError(data.id); throw new error.ItemNotFoundError(data.id);
} else if (!row.enabled) { } else if (!row.enabled) {
throw new error.ValidationError('Host is already disabled'); throw new error.ValidationError('Host is already disabled');
@ -410,16 +403,16 @@ const internalRedirectionHost = {
.where('is_deleted', 0) .where('is_deleted', 0)
.groupBy('id') .groupBy('id')
.allowGraph('[owner,certificate]') .allowGraph('[owner,certificate]')
.orderBy(castJsonIfNeed('domain_names'), 'ASC'); .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));
} }
// Query is used for searching // Query is used for searching
if (typeof search_query === 'string' && search_query.length > 0) { if (typeof search_query === 'string') {
query.where(function () { query.where(function () {
this.where(castJsonIfNeed('domain_names'), 'like', `%${search_query}%`); this.where('domain_names', 'like', '%' + search_query + '%');
}); });
} }

View File

@ -4,12 +4,9 @@ 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');
const internalCertificate = require('./certificate');
const internalHost = require('./host');
const {castJsonIfNeed} = require('../lib/helpers');
function omissions () { function omissions () {
return ['is_deleted', 'owner.is_deleted', 'certificate.is_deleted']; return ['is_deleted'];
} }
const internalStream = { const internalStream = {
@ -20,12 +17,6 @@ const internalStream = {
* @returns {Promise} * @returns {Promise}
*/ */
create: (access, data) => { create: (access, data) => {
const create_certificate = data.certificate_id === 'new';
if (create_certificate) {
delete data.certificate_id;
}
return access.can('streams:create', data) return access.can('streams:create', data)
.then((/*access_data*/) => { .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
@ -35,44 +26,16 @@ const internalStream = {
data.meta = {}; data.meta = {};
} }
// streams aren't routed by domain name so don't store domain names in the DB
let data_no_domains = structuredClone(data);
delete data_no_domains.domain_names;
return streamModel return streamModel
.query() .query()
.insertAndFetch(data_no_domains) .insertAndFetch(data)
.then(utils.omitRow(omissions())); .then(utils.omitRow(omissions()));
}) })
.then((row) => {
if (create_certificate) {
return internalCertificate.createQuickCertificate(access, data)
.then((cert) => {
// update host with cert id
return internalStream.update(access, {
id: row.id,
certificate_id: cert.id
});
})
.then(() => {
return row;
});
} else {
return row;
}
})
.then((row) => {
// re-fetch with cert
return internalStream.get(access, {
id: row.id,
expand: ['certificate', 'owner']
});
})
.then((row) => { .then((row) => {
// Configure nginx // Configure nginx
return internalNginx.configure(streamModel, 'stream', row) return internalNginx.configure(streamModel, 'stream', row)
.then(() => { .then(() => {
return row; return internalStream.get(access, {id: row.id, expand: ['owner']});
}); });
}) })
.then((row) => { .then((row) => {
@ -96,12 +59,6 @@ const internalStream = {
* @return {Promise} * @return {Promise}
*/ */
update: (access, data) => { update: (access, data) => {
const create_certificate = data.certificate_id === 'new';
if (create_certificate) {
delete data.certificate_id;
}
return access.can('streams:update', data.id) return access.can('streams:update', data.id)
.then((/*access_data*/) => { .then((/*access_data*/) => {
// TODO: at this point the existing streams should have been checked // TODO: at this point the existing streams should have been checked
@ -113,32 +70,16 @@ const internalStream = {
throw new error.InternalValidationError('Stream could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id); throw new error.InternalValidationError('Stream could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
} }
if (create_certificate) {
return internalCertificate.createQuickCertificate(access, {
domain_names: data.domain_names || row.domain_names,
meta: _.assign({}, row.meta, data.meta)
})
.then((cert) => {
// update host with cert id
data.certificate_id = cert.id;
})
.then(() => {
return row;
});
} else {
return row;
}
})
.then((row) => {
// Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here.
data = _.assign({}, {
domain_names: row.domain_names
}, data);
return streamModel return streamModel
.query() .query()
.patchAndFetchById(row.id, data) .patchAndFetchById(row.id, data)
.then(utils.omitRow(omissions())) .then(utils.omitRow(omissions()))
.then((saved_row) => {
return internalNginx.configure(streamModel, 'stream', saved_row)
.then(() => {
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.add(access, {
@ -151,17 +92,6 @@ const internalStream = {
return saved_row; return saved_row;
}); });
}); });
})
.then(() => {
return internalStream.get(access, {id: data.id, expand: ['owner', 'certificate']})
.then((row) => {
return internalNginx.configure(streamModel, 'stream', row)
.then((new_meta) => {
row.meta = new_meta;
row = internalHost.cleanRowCertificateMeta(row);
return _.omit(row, omissions());
});
});
}); });
}, },
@ -184,7 +114,7 @@ const internalStream = {
.query() .query()
.where('is_deleted', 0) .where('is_deleted', 0)
.andWhere('id', data.id) .andWhere('id', data.id)
.allowGraph('[owner,certificate]') .allowGraph('[owner]')
.first(); .first();
if (access_data.permission_visibility !== 'all') { if (access_data.permission_visibility !== 'all') {
@ -198,10 +128,9 @@ const internalStream = {
return query.then(utils.omitRow(omissions())); return query.then(utils.omitRow(omissions()));
}) })
.then((row) => { .then((row) => {
if (!row || !row.id) { if (!row) {
throw new error.ItemNotFoundError(data.id); throw new error.ItemNotFoundError(data.id);
} }
row = internalHost.cleanRowCertificateMeta(row);
// Custom omissions // Custom omissions
if (typeof data.omit !== 'undefined' && data.omit !== null) { if (typeof data.omit !== 'undefined' && data.omit !== null) {
row = _.omit(row, data.omit); row = _.omit(row, data.omit);
@ -223,7 +152,7 @@ const internalStream = {
return internalStream.get(access, {id: data.id}); return internalStream.get(access, {id: data.id});
}) })
.then((row) => { .then((row) => {
if (!row || !row.id) { if (!row) {
throw new error.ItemNotFoundError(data.id); throw new error.ItemNotFoundError(data.id);
} }
@ -267,14 +196,14 @@ const internalStream = {
.then(() => { .then(() => {
return internalStream.get(access, { return internalStream.get(access, {
id: data.id, id: data.id,
expand: ['certificate', 'owner'] expand: ['owner']
}); });
}) })
.then((row) => { .then((row) => {
if (!row || !row.id) { if (!row) {
throw new error.ItemNotFoundError(data.id); throw new error.ItemNotFoundError(data.id);
} else if (row.enabled) { } else if (row.enabled) {
throw new error.ValidationError('Stream is already enabled'); throw new error.ValidationError('Host is already enabled');
} }
row.enabled = 1; row.enabled = 1;
@ -317,10 +246,10 @@ const internalStream = {
return internalStream.get(access, {id: data.id}); return internalStream.get(access, {id: data.id});
}) })
.then((row) => { .then((row) => {
if (!row || !row.id) { if (!row) {
throw new error.ItemNotFoundError(data.id); throw new error.ItemNotFoundError(data.id);
} else if (!row.enabled) { } else if (!row.enabled) {
throw new error.ValidationError('Stream is already disabled'); throw new error.ValidationError('Host is already disabled');
} }
row.enabled = 0; row.enabled = 0;
@ -364,21 +293,21 @@ const internalStream = {
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 let query = streamModel
.query() .query()
.where('is_deleted', 0) .where('is_deleted', 0)
.groupBy('id') .groupBy('id')
.allowGraph('[owner,certificate]') .allowGraph('[owner]')
.orderByRaw('CAST(incoming_port AS INTEGER) ASC'); .orderBy('incoming_port', 'ASC');
if (access_data.permission_visibility !== 'all') { if (access_data.permission_visibility !== 'all') {
query.andWhere('owner_user_id', access.token.getUserId(1)); query.andWhere('owner_user_id', access.token.getUserId(1));
} }
// Query is used for searching // Query is used for searching
if (typeof search_query === 'string' && search_query.length > 0) { if (typeof search_query === 'string') {
query.where(function () { query.where(function () {
this.where(castJsonIfNeed('incoming_port'), 'like', `%${search_query}%`); this.where('incoming_port', 'like', '%' + search_query + '%');
}); });
} }
@ -387,13 +316,6 @@ const internalStream = {
} }
return query.then(utils.omitRows(omissions())); return query.then(utils.omitRows(omissions()));
})
.then((rows) => {
if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) {
return internalHost.cleanAllRowsCertificateMeta(rows);
}
return rows;
}); });
}, },
@ -405,9 +327,9 @@ const internalStream = {
* @returns {Promise} * @returns {Promise}
*/ */
getCount: (user_id, visibility) => { getCount: (user_id, visibility) => {
const query = streamModel let query = streamModel
.query() .query()
.count('id AS count') .count('id as count')
.where('is_deleted', 0); .where('is_deleted', 0);
if (visibility !== 'all') { if (visibility !== 'all') {

View File

@ -5,8 +5,6 @@ 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');
const ERROR_MESSAGE_INVALID_AUTH = 'Invalid email or password';
module.exports = { module.exports = {
/** /**
@ -71,15 +69,15 @@ module.exports = {
}; };
}); });
} else { } else {
throw new error.AuthError(ERROR_MESSAGE_INVALID_AUTH); throw new error.AuthError('Invalid password');
} }
}); });
} else { } else {
throw new error.AuthError(ERROR_MESSAGE_INVALID_AUTH); throw new error.AuthError('No password auth for user');
} }
}); });
} else { } else {
throw new error.AuthError(ERROR_MESSAGE_INVALID_AUTH); throw new error.AuthError('No relevant user found');
} }
}); });
}, },

View File

@ -194,7 +194,7 @@ const internalUser = {
return query.then(utils.omitRow(omissions())); return query.then(utils.omitRow(omissions()));
}) })
.then((row) => { .then((row) => {
if (!row || !row.id) { if (!row) {
throw new error.ItemNotFoundError(data.id); throw new error.ItemNotFoundError(data.id);
} }
// Custom omissions // Custom omissions

View File

@ -1,6 +1,6 @@
module.exports = { module.exports = {
development: { development: {
client: 'mysql2', client: 'mysql',
migrations: { migrations: {
tableName: 'migrations', tableName: 'migrations',
stub: 'lib/migrate_template.js', stub: 'lib/migrate_template.js',
@ -9,7 +9,7 @@ module.exports = {
}, },
production: { production: {
client: 'mysql2', client: 'mysql',
migrations: { migrations: {
tableName: 'migrations', tableName: 'migrations',
stub: 'lib/migrate_template.js', stub: 'lib/migrate_template.js',

View File

@ -10,7 +10,7 @@
const _ = require('lodash'); const _ = require('lodash');
const logger = require('../logger').access; const logger = require('../logger').access;
const Ajv = require('ajv/dist/2020'); 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');
@ -174,6 +174,7 @@ module.exports = function (token_string) {
let schema = { let schema = {
$id: 'objects', $id: 'objects',
$schema: 'http://json-schema.org/draft-07/schema#',
description: 'Actor Properties', description: 'Actor Properties',
type: 'object', type: 'object',
additionalProperties: false, additionalProperties: false,
@ -250,7 +251,7 @@ module.exports = function (token_string) {
// Initialised, token decoded ok // Initialised, 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: data,
scope: Token.get('scope'), scope: Token.get('scope'),
@ -266,18 +267,24 @@ module.exports = function (token_string) {
}; };
let permissionSchema = { let permissionSchema = {
$schema: 'http://json-schema.org/draft-07/schema#',
$async: true, $async: true,
$id: 'permissions', $id: 'permissions',
type: 'object',
additionalProperties: false, additionalProperties: false,
properties: {} properties: {}
}; };
permissionSchema.properties[permission] = require('./access/' + permission.replace(/:/gim, '-') + '.json'); permissionSchema.properties[permission] = require('./access/' + permission.replace(/:/gim, '-') + '.json');
const ajv = new Ajv({ // logger.info('objectSchema', JSON.stringify(objectSchema, null, 2));
// logger.info('permissionSchema', JSON.stringify(permissionSchema, null, 2));
// logger.info('data_schema', JSON.stringify(data_schema, null, 2));
let ajv = validator({
verbose: true, verbose: true,
allErrors: true, allErrors: true,
format: 'full',
missingRefs: 'fail',
breakOnError: true, breakOnError: true,
coerceTypes: true, coerceTypes: true,
schemas: [ schemas: [

View File

@ -1,4 +1,5 @@
{ {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "perms", "$id": "perms",
"definitions": { "definitions": {
"view": { "view": {

View File

@ -1,4 +1,5 @@
{ {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "roles", "$id": "roles",
"definitions": { "definitions": {
"admin": { "admin": {

View File

@ -1,78 +0,0 @@
const dnsPlugins = require('../global/certbot-dns-plugins.json');
const utils = require('./utils');
const error = require('./error');
const logger = require('../logger').certbot;
const batchflow = require('batchflow');
const CERTBOT_VERSION_REPLACEMENT = '$(certbot --version | grep -Eo \'[0-9](\\.[0-9]+)+\')';
const certbot = {
/**
* @param {array} pluginKeys
*/
installPlugins: async function (pluginKeys) {
let hasErrors = false;
return new Promise((resolve, reject) => {
if (pluginKeys.length === 0) {
resolve();
return;
}
batchflow(pluginKeys).sequential()
.each((i, pluginKey, next) => {
certbot.installPlugin(pluginKey)
.then(() => {
next();
})
.catch((err) => {
hasErrors = true;
next(err);
});
})
.error((err) => {
logger.error(err.message);
})
.end(() => {
if (hasErrors) {
reject(new error.CommandError('Some plugins failed to install. Please check the logs above', 1));
} else {
resolve();
}
});
});
},
/**
* Installs a cerbot plugin given the key for the object from
* ../global/certbot-dns-plugins.json
*
* @param {string} pluginKey
* @returns {Object}
*/
installPlugin: async function (pluginKey) {
if (typeof dnsPlugins[pluginKey] === 'undefined') {
// throw Error(`Certbot plugin ${pluginKey} not found`);
throw new error.ItemNotFoundError(pluginKey);
}
const plugin = dnsPlugins[pluginKey];
logger.start(`Installing ${pluginKey}...`);
plugin.version = plugin.version.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT);
plugin.dependencies = plugin.dependencies.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT);
const cmd = '. /opt/certbot/bin/activate && pip install --no-cache-dir ' + plugin.dependencies + ' ' + plugin.package_name + plugin.version + ' ' + ' && deactivate';
return utils.exec(cmd)
.then((result) => {
logger.complete(`Installed ${pluginKey}`);
return result;
})
.catch((err) => {
throw err;
});
},
};
module.exports = certbot;

View File

@ -3,9 +3,6 @@ const NodeRSA = require('node-rsa');
const logger = require('../logger').global; const logger = require('../logger').global;
const keysFile = '/data/keys.json'; const keysFile = '/data/keys.json';
const mysqlEngine = 'mysql2';
const postgresEngine = 'pg';
const sqliteClientName = 'sqlite3';
let instance = null; let instance = null;
@ -17,7 +14,7 @@ const configure = () => {
let configData; let configData;
try { try {
configData = require(filename); configData = require(filename);
} catch (_) { } catch (err) {
// do nothing // do nothing
} }
@ -37,7 +34,7 @@ const configure = () => {
logger.info('Using MySQL configuration'); logger.info('Using MySQL configuration');
instance = { instance = {
database: { database: {
engine: mysqlEngine, engine: 'mysql',
host: envMysqlHost, host: envMysqlHost,
port: process.env.DB_MYSQL_PORT || 3306, port: process.env.DB_MYSQL_PORT || 3306,
user: envMysqlUser, user: envMysqlUser,
@ -49,33 +46,13 @@ const configure = () => {
return; return;
} }
const envPostgresHost = process.env.DB_POSTGRES_HOST || null;
const envPostgresUser = process.env.DB_POSTGRES_USER || null;
const envPostgresName = process.env.DB_POSTGRES_NAME || null;
if (envPostgresHost && envPostgresUser && envPostgresName) {
// we have enough postgres creds to go with postgres
logger.info('Using Postgres configuration');
instance = {
database: {
engine: postgresEngine,
host: envPostgresHost,
port: process.env.DB_POSTGRES_PORT || 5432,
user: envPostgresUser,
password: process.env.DB_POSTGRES_PASSWORD,
name: envPostgresName,
},
keys: getKeys(),
};
return;
}
const envSqliteFile = process.env.DB_SQLITE_FILE || '/data/database.sqlite'; const envSqliteFile = process.env.DB_SQLITE_FILE || '/data/database.sqlite';
logger.info(`Using Sqlite: ${envSqliteFile}`); logger.info(`Using Sqlite: ${envSqliteFile}`);
instance = { instance = {
database: { database: {
engine: 'knex-native', engine: 'knex-native',
knex: { knex: {
client: sqliteClientName, client: 'sqlite3',
connection: { connection: {
filename: envSqliteFile filename: envSqliteFile
}, },
@ -116,7 +93,7 @@ const generateKeys = () => {
try { try {
fs.writeFileSync(keysFile, JSON.stringify(keys, null, 2)); fs.writeFileSync(keysFile, JSON.stringify(keys, null, 2));
} catch (err) { } catch (err) {
logger.error('Could not write JWT key pair to config file: ' + keysFile + ': ' + err.message); logger.error('Could not write JWT key pair to config file: ' + keysFile + ': ' . err.message);
process.exit(1); process.exit(1);
} }
logger.info('Wrote JWT key pair to config file: ' + keysFile); logger.info('Wrote JWT key pair to config file: ' + keysFile);
@ -166,27 +143,7 @@ module.exports = {
*/ */
isSqlite: function () { isSqlite: function () {
instance === null && configure(); instance === null && configure();
return instance.database.knex && instance.database.knex.client === sqliteClientName; return instance.database.knex && instance.database.knex.client === 'sqlite3';
},
/**
* Is this a mysql configuration?
*
* @returns {boolean}
*/
isMysql: function () {
instance === null && configure();
return instance.database.engine === mysqlEngine;
},
/**
* Is this a postgres configuration?
*
* @returns {boolean}
*/
isPostgres: function () {
instance === null && configure();
return instance.database.engine === postgresEngine;
}, },
/** /**
@ -223,15 +180,5 @@ module.exports = {
*/ */
useLetsencryptStaging: function () { useLetsencryptStaging: function () {
return !!process.env.LE_STAGING; return !!process.env.LE_STAGING;
},
/**
* @returns {string|null}
*/
useLetsencryptServer: function () {
if (process.env.LE_SERVER) {
return process.env.LE_SERVER;
}
return null;
} }
}; };

View File

@ -82,16 +82,7 @@ module.exports = {
this.message = message; this.message = message;
this.public = false; this.public = false;
this.status = 400; this.status = 400;
}, }
CommandError: function (stdErr, code, previous) {
Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name;
this.previous = previous;
this.message = stdErr;
this.code = code;
this.public = false;
},
}; };
_.forEach(module.exports, function (error) { _.forEach(module.exports, function (error) {

View File

@ -1,5 +1,25 @@
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 = {
oneOf: [
{
type: 'string',
pattern: '^[a-z\\-]+:\\/\\/(?:[\\w\\-\\.]+(:[0-9]+)?/?)?$'
},
{
type: 'string',
pattern: '^[a-z\\-]+:\\/\\/(?:\\[([a-z0-9]{0,4}\\:?)+\\])?/?(:[0-9]+)?$'
}
]
};
// very relaxed validation....
validator(originSchema, req.headers.origin)
.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,
@ -9,8 +29,12 @@ module.exports = function (req, res, next) {
'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);
} else { } else {
// No origin // No origin
next(); next();
} }
}; };

View File

@ -1,6 +1,4 @@
const moment = require('moment'); const moment = require('moment');
const {isPostgres} = require('./config');
const {ref} = require('objection');
module.exports = { module.exports = {
@ -29,34 +27,6 @@ module.exports = {
} }
return null; return null;
},
convertIntFieldsToBool: function (obj, fields) {
fields.forEach(function (field) {
if (typeof obj[field] !== 'undefined') {
obj[field] = obj[field] === 1;
}
});
return obj;
},
convertBoolFieldsToInt: function (obj, fields) {
fields.forEach(function (field) {
if (typeof obj[field] !== 'undefined') {
obj[field] = obj[field] ? 1 : 0;
}
});
return obj;
},
/**
* Casts a column to json if using postgres
*
* @param {string} colName
* @returns {string|Objection.ReferenceBuilder}
*/
castJsonIfNeed: function (colName) {
return isPostgres() ? ref(colName).castText() : colName;
} }
}; };

View File

@ -3,27 +3,23 @@ const exec = require('child_process').exec;
const execFile = require('child_process').execFile; const execFile = require('child_process').execFile;
const { Liquid } = require('liquidjs'); const { Liquid } = require('liquidjs');
const logger = require('../logger').global; const logger = require('../logger').global;
const error = require('./error');
module.exports = { module.exports = {
exec: async function(cmd, options = {}) { /**
logger.debug('CMD:', cmd); * @param {String} cmd
* @returns {Promise}
const { stdout, stderr } = await new Promise((resolve, reject) => { */
const child = exec(cmd, options, (isError, stdout, stderr) => { exec: function (cmd) {
if (isError) { return new Promise((resolve, reject) => {
reject(new error.CommandError(stderr, isError)); exec(cmd, function (err, stdout, /*stderr*/) {
if (err && typeof err === 'object') {
reject(err);
} else { } else {
resolve({ stdout, stderr }); resolve(stdout.trim());
} }
}); });
child.on('error', (e) => {
reject(new error.CommandError(stderr, 1, e));
}); });
});
return stdout;
}, },
/** /**
@ -32,8 +28,7 @@ module.exports = {
* @returns {Promise} * @returns {Promise}
*/ */
execFile: function (cmd, args) { execFile: function (cmd, args) {
// logger.debug('CMD: ' + cmd + ' ' + (args ? args.join(' ') : '')); logger.debug('CMD: ' + cmd + ' ' + (args ? args.join(' ') : ''));
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
execFile(cmd, args, function (err, stdout, /*stderr*/) { execFile(cmd, args, function (err, stdout, /*stderr*/) {
if (err && typeof err === 'object') { if (err && typeof err === 'object') {

View File

@ -1,12 +1,13 @@
const Ajv = require('ajv/dist/2020');
const error = require('../error'); const error = require('../error');
const path = require('path');
const parser = require('json-schema-ref-parser');
const ajv = new Ajv({ const ajv = require('ajv')({
verbose: true, verbose: true,
allErrors: true, validateSchema: true,
allowUnionTypes: true, allErrors: false,
strict: false, format: 'full',
coerceTypes: true, coerceTypes: true
}); });
/** /**
@ -16,18 +17,12 @@ const ajv = new Ajv({
*/ */
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 (schema === null) {
reject(new error.ValidationError('Schema is undefined'));
return;
}
if (typeof payload === 'undefined') { if (typeof payload === 'undefined') {
reject(new error.ValidationError('Payload is undefined')); reject(new error.ValidationError('Payload is undefined'));
return;
} }
const validate = ajv.compile(schema); let validate = ajv.compile(schema);
const valid = validate(payload); let valid = validate(payload);
if (valid && !validate.errors) { if (valid && !validate.errors) {
resolve(payload); resolve(payload);
@ -40,4 +35,11 @@ function apiValidator (schema, payload/*, description*/) {
}); });
} }
apiValidator.loadSchemas = parser
.dereference(path.resolve('schema/index.json'))
.then((schema) => {
ajv.addSchema(schema);
return schema;
});
module.exports = apiValidator; module.exports = apiValidator;

View File

@ -1,17 +1,17 @@
const _ = require('lodash'); const _ = require('lodash');
const Ajv = require('ajv/dist/2020');
const error = require('../error'); const error = require('../error');
const commonDefinitions = require('../../schema/common.json'); const definitions = require('../../schema/definitions.json');
RegExp.prototype.toJSON = RegExp.prototype.toString; RegExp.prototype.toJSON = RegExp.prototype.toString;
const ajv = new Ajv({ const ajv = require('ajv')({
verbose: true, verbose: true,
allErrors: true, allErrors: true,
allowUnionTypes: true, format: 'full', // strict regexes for format checks
coerceTypes: true, coerceTypes: true,
strict: false, schemas: [
schemas: [commonDefinitions] definitions
]
}); });
/** /**
@ -27,19 +27,23 @@ function validator (schema, payload) {
} else { } else {
try { try {
let validate = ajv.compile(schema); let validate = ajv.compile(schema);
let valid = validate(payload);
let 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); let 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

@ -7,7 +7,6 @@ module.exports = {
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 '}),
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,38 +0,0 @@
const migrate_name = 'stream_ssl';
const logger = require('../logger').migrate;
/**
* Migrate
*
* @see http://knexjs.org/#Schema
*
* @param {Object} knex
* @returns {Promise}
*/
exports.up = function (knex) {
logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema.table('stream', (table) => {
table.integer('certificate_id').notNull().unsigned().defaultTo(0);
})
.then(function () {
logger.info('[' + migrate_name + '] stream Table altered');
});
};
/**
* Undo Migrate
*
* @param {Object} knex
* @returns {Promise}
*/
exports.down = function (knex) {
logger.info('[' + migrate_name + '] Migrating Down...');
return knex.schema.table('stream', (table) => {
table.dropColumn('certificate_id');
})
.then(function () {
logger.info('[' + migrate_name + '] stream Table altered');
});
};

View File

@ -2,7 +2,6 @@
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
const db = require('../db'); const db = require('../db');
const helpers = require('../lib/helpers');
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');
@ -11,12 +10,6 @@ const now = require('./now_helper');
Model.knex(db); Model.knex(db);
const boolFields = [
'is_deleted',
'satisfy_any',
'pass_auth',
];
class AccessList extends Model { class AccessList extends Model {
$beforeInsert () { $beforeInsert () {
this.created_on = now(); this.created_on = now();
@ -32,16 +25,6 @@ class AccessList extends Model {
this.modified_on = now(); this.modified_on = now();
} }
$parseDatabaseJson(json) {
json = super.$parseDatabaseJson(json);
return helpers.convertIntFieldsToBool(json, boolFields);
}
$formatDatabaseJson(json) {
json = helpers.convertBoolFieldsToInt(json, boolFields);
return super.$formatDatabaseJson(json);
}
static get name () { static get name () {
return 'AccessList'; return 'AccessList';
} }

View File

@ -3,17 +3,12 @@
const bcrypt = require('bcrypt'); const bcrypt = require('bcrypt');
const db = require('../db'); const db = require('../db');
const helpers = require('../lib/helpers');
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);
const boolFields = [
'is_deleted',
];
function encryptPassword () { function encryptPassword () {
/* jshint -W040 */ /* jshint -W040 */
let _this = this; let _this = this;
@ -46,16 +41,6 @@ class Auth extends Model {
return encryptPassword.apply(this, queryContext); return encryptPassword.apply(this, queryContext);
} }
$parseDatabaseJson(json) {
json = super.$parseDatabaseJson(json);
return helpers.convertIntFieldsToBool(json, boolFields);
}
$formatDatabaseJson(json) {
json = helpers.convertBoolFieldsToInt(json, boolFields);
return super.$formatDatabaseJson(json);
}
/** /**
* Verify a plain password against the encrypted password * Verify a plain password against the encrypted password
* *

View File

@ -2,16 +2,12 @@
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
const db = require('../db'); const db = require('../db');
const helpers = require('../lib/helpers');
const Model = require('objection').Model; const Model = require('objection').Model;
const User = require('./user');
const now = require('./now_helper'); const now = require('./now_helper');
Model.knex(db); Model.knex(db);
const boolFields = [
'is_deleted',
];
class Certificate extends Model { class Certificate extends Model {
$beforeInsert () { $beforeInsert () {
this.created_on = now(); this.created_on = now();
@ -44,16 +40,6 @@ class Certificate extends Model {
} }
} }
$parseDatabaseJson(json) {
json = super.$parseDatabaseJson(json);
return helpers.convertIntFieldsToBool(json, boolFields);
}
$formatDatabaseJson(json) {
json = helpers.convertBoolFieldsToInt(json, boolFields);
return super.$formatDatabaseJson(json);
}
static get name () { static get name () {
return 'Certificate'; return 'Certificate';
} }
@ -67,11 +53,6 @@ class Certificate extends Model {
} }
static get relationMappings () { static get relationMappings () {
const ProxyHost = require('./proxy_host');
const DeadHost = require('./dead_host');
const User = require('./user');
const RedirectionHost = require('./redirection_host');
return { return {
owner: { owner: {
relation: Model.HasOneRelation, relation: Model.HasOneRelation,
@ -83,39 +64,6 @@ class Certificate extends Model {
modify: function (qb) { modify: function (qb) {
qb.where('user.is_deleted', 0); qb.where('user.is_deleted', 0);
} }
},
proxy_hosts: {
relation: Model.HasManyRelation,
modelClass: ProxyHost,
join: {
from: 'certificate.id',
to: 'proxy_host.certificate_id'
},
modify: function (qb) {
qb.where('proxy_host.is_deleted', 0);
}
},
dead_hosts: {
relation: Model.HasManyRelation,
modelClass: DeadHost,
join: {
from: 'certificate.id',
to: 'dead_host.certificate_id'
},
modify: function (qb) {
qb.where('dead_host.is_deleted', 0);
}
},
redirection_hosts: {
relation: Model.HasManyRelation,
modelClass: RedirectionHost,
join: {
from: 'certificate.id',
to: 'redirection_host.certificate_id'
},
modify: function (qb) {
qb.where('redirection_host.is_deleted', 0);
}
} }
}; };
} }

View File

@ -2,7 +2,6 @@
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
const db = require('../db'); const db = require('../db');
const helpers = require('../lib/helpers');
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');
@ -10,15 +9,6 @@ const now = require('./now_helper');
Model.knex(db); Model.knex(db);
const boolFields = [
'is_deleted',
'ssl_forced',
'http2_support',
'enabled',
'hsts_enabled',
'hsts_subdomains',
];
class DeadHost extends Model { class DeadHost extends Model {
$beforeInsert () { $beforeInsert () {
this.created_on = now(); this.created_on = now();
@ -46,16 +36,6 @@ class DeadHost extends Model {
} }
} }
$parseDatabaseJson(json) {
json = super.$parseDatabaseJson(json);
return helpers.convertIntFieldsToBool(json, boolFields);
}
$formatDatabaseJson(json) {
json = helpers.convertBoolFieldsToInt(json, boolFields);
return super.$formatDatabaseJson(json);
}
static get name () { static get name () {
return 'DeadHost'; return 'DeadHost';
} }

View File

@ -2,7 +2,6 @@
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
const db = require('../db'); const db = require('../db');
const helpers = require('../lib/helpers');
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');
@ -11,18 +10,6 @@ const now = require('./now_helper');
Model.knex(db); Model.knex(db);
const boolFields = [
'is_deleted',
'ssl_forced',
'caching_enabled',
'block_exploits',
'allow_websocket_upgrade',
'http2_support',
'enabled',
'hsts_enabled',
'hsts_subdomains',
];
class ProxyHost extends Model { class ProxyHost extends Model {
$beforeInsert () { $beforeInsert () {
this.created_on = now(); this.created_on = now();
@ -50,16 +37,6 @@ class ProxyHost extends Model {
} }
} }
$parseDatabaseJson(json) {
json = super.$parseDatabaseJson(json);
return helpers.convertIntFieldsToBool(json, boolFields);
}
$formatDatabaseJson(json) {
json = helpers.convertBoolFieldsToInt(json, boolFields);
return super.$formatDatabaseJson(json);
}
static get name () { static get name () {
return 'ProxyHost'; return 'ProxyHost';
} }

View File

@ -3,7 +3,6 @@
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
const db = require('../db'); const db = require('../db');
const helpers = require('../lib/helpers');
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');
@ -11,17 +10,6 @@ const now = require('./now_helper');
Model.knex(db); Model.knex(db);
const boolFields = [
'is_deleted',
'enabled',
'preserve_path',
'ssl_forced',
'block_exploits',
'hsts_enabled',
'hsts_subdomains',
'http2_support',
];
class RedirectionHost extends Model { class RedirectionHost extends Model {
$beforeInsert () { $beforeInsert () {
this.created_on = now(); this.created_on = now();
@ -49,16 +37,6 @@ class RedirectionHost extends Model {
} }
} }
$parseDatabaseJson(json) {
json = super.$parseDatabaseJson(json);
return helpers.convertIntFieldsToBool(json, boolFields);
}
$formatDatabaseJson(json) {
json = helpers.convertBoolFieldsToInt(json, boolFields);
return super.$formatDatabaseJson(json);
}
static get name () { static get name () {
return 'RedirectionHost'; return 'RedirectionHost';
} }

View File

@ -1,19 +1,13 @@
const Model = require('objection').Model; // Objection Docs:
// http://vincit.github.io/objection.js/
const db = require('../db'); const db = require('../db');
const helpers = require('../lib/helpers'); const Model = require('objection').Model;
const User = require('./user'); const User = require('./user');
const Certificate = require('./certificate');
const now = require('./now_helper'); const now = require('./now_helper');
Model.knex(db); Model.knex(db);
const boolFields = [
'is_deleted',
'enabled',
'tcp_forwarding',
'udp_forwarding',
];
class Stream extends Model { class Stream extends Model {
$beforeInsert () { $beforeInsert () {
this.created_on = now(); this.created_on = now();
@ -29,16 +23,6 @@ class Stream extends Model {
this.modified_on = now(); this.modified_on = now();
} }
$parseDatabaseJson(json) {
json = super.$parseDatabaseJson(json);
return helpers.convertIntFieldsToBool(json, boolFields);
}
$formatDatabaseJson(json) {
json = helpers.convertBoolFieldsToInt(json, boolFields);
return super.$formatDatabaseJson(json);
}
static get name () { static get name () {
return 'Stream'; return 'Stream';
} }
@ -63,17 +47,6 @@ class Stream extends Model {
modify: function (qb) { modify: function (qb) {
qb.where('user.is_deleted', 0); qb.where('user.is_deleted', 0);
} }
},
certificate: {
relation: Model.HasOneRelation,
modelClass: Certificate,
join: {
from: 'stream.certificate_id',
to: 'certificate.id'
},
modify: function (qb) {
qb.where('certificate.is_deleted', 0);
}
} }
}; };
} }

View File

@ -2,18 +2,12 @@
// http://vincit.github.io/objection.js/ // http://vincit.github.io/objection.js/
const db = require('../db'); const db = require('../db');
const helpers = require('../lib/helpers');
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);
const boolFields = [
'is_deleted',
'is_disabled',
];
class User extends Model { class User extends Model {
$beforeInsert () { $beforeInsert () {
this.created_on = now(); this.created_on = now();
@ -29,16 +23,6 @@ class User extends Model {
this.modified_on = now(); this.modified_on = now();
} }
$parseDatabaseJson(json) {
json = super.$parseDatabaseJson(json);
return helpers.convertIntFieldsToBool(json, boolFields);
}
$formatDatabaseJson(json) {
json = helpers.convertBoolFieldsToInt(json, boolFields);
return super.$formatDatabaseJson(json);
}
static get name () { static get name () {
return 'User'; return 'User';
} }

View File

@ -2,28 +2,27 @@
"name": "nginx-proxy-manager", "name": "nginx-proxy-manager",
"version": "0.0.0", "version": "0.0.0",
"description": "A beautiful interface for creating Nginx endpoints", "description": "A beautiful interface for creating Nginx endpoints",
"main": "index.js", "main": "js/index.js",
"dependencies": { "dependencies": {
"@apidevtools/json-schema-ref-parser": "^11.7.0", "ajv": "^6.12.0",
"ajv": "^8.17.1",
"archiver": "^5.3.0", "archiver": "^5.3.0",
"batchflow": "^0.4.0", "batchflow": "^0.4.0",
"bcrypt": "^5.0.0", "bcrypt": "^5.0.0",
"body-parser": "^1.20.3", "body-parser": "^1.19.0",
"compression": "^1.7.4", "compression": "^1.7.4",
"express": "^4.20.0", "express": "^4.17.3",
"express-fileupload": "^1.1.9", "express-fileupload": "^1.1.9",
"gravatar": "^1.8.0", "gravatar": "^1.8.0",
"json-schema-ref-parser": "^8.0.0",
"jsonwebtoken": "^9.0.0", "jsonwebtoken": "^9.0.0",
"knex": "2.4.2", "knex": "2.4.2",
"liquidjs": "10.6.1", "liquidjs": "10.6.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"moment": "^2.29.4", "moment": "^2.29.4",
"mysql2": "^3.11.1", "mysql": "^2.18.1",
"node-rsa": "^1.0.8", "node-rsa": "^1.0.8",
"objection": "3.0.1", "objection": "3.0.1",
"path": "^0.12.7", "path": "^0.12.7",
"pg": "^8.13.1",
"signale": "1.4.0", "signale": "1.4.0",
"sqlite3": "5.1.6", "sqlite3": "5.1.6",
"temp-write": "^4.0.0" "temp-write": "^4.0.0"
@ -35,14 +34,9 @@
"author": "Jamie Curnow <jc@jc21.com>", "author": "Jamie Curnow <jc@jc21.com>",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@apidevtools/swagger-parser": "^10.1.0",
"chalk": "4.1.2",
"eslint": "^8.36.0", "eslint": "^8.36.0",
"eslint-plugin-align-assignments": "^1.1.2", "eslint-plugin-align-assignments": "^1.1.2",
"nodemon": "^2.0.2", "nodemon": "^2.0.2",
"prettier": "^2.0.4" "prettier": "^2.0.4"
},
"scripts": {
"validate-schema": "node validate-schema.js"
} }
} }

View File

@ -1,7 +1,7 @@
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({ let router = express.Router({
caseSensitive: true, caseSensitive: true,
@ -14,7 +14,7 @@ let router = express.Router({
*/ */
router router
.route('/') .route('/')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())
@ -29,10 +29,10 @@ router
additionalProperties: false, additionalProperties: false,
properties: { properties: {
expand: { expand: {
$ref: 'common#/properties/expand' $ref: 'definitions#/definitions/expand'
}, },
query: { query: {
$ref: 'common#/properties/query' $ref: 'definitions#/definitions/query'
} }
} }
}, { }, {

View File

@ -1,6 +1,6 @@
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({ let router = express.Router({
caseSensitive: true, caseSensitive: true,
@ -43,7 +43,7 @@ router.use('/nginx/certificates', require('./nginx/certificates'));
* *
* ALL /api/* * ALL /api/*
*/ */
router.all(/(.+)/, function (req, _, next) { router.all(/(.+)/, function (req, res, next) {
req.params.page = req.params['0']; req.params.page = req.params['0'];
next(new error.ItemNotFoundError(req.params.page)); next(new error.ItemNotFoundError(req.params.page));
}); });

View File

@ -1,9 +1,8 @@
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 apiValidator = require('../../lib/validator/api'); const internalAccessList = require('../../../internal/access-list');
const internalAccessList = require('../../internal/access-list'); const apiValidator = require('../../../lib/validator/api');
const schema = require('../../schema');
let router = express.Router({ let router = express.Router({
caseSensitive: true, caseSensitive: true,
@ -31,10 +30,10 @@ router
additionalProperties: false, additionalProperties: false,
properties: { properties: {
expand: { expand: {
$ref: 'common#/properties/expand' $ref: 'definitions#/definitions/expand'
}, },
query: { query: {
$ref: 'common#/properties/query' $ref: 'definitions#/definitions/query'
} }
} }
}, { }, {
@ -57,7 +56,7 @@ router
* Create a new access-list * Create a new access-list
*/ */
.post((req, res, next) => { .post((req, res, next) => {
apiValidator(schema.getValidationSchema('/nginx/access-lists', 'post'), 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);
}) })
@ -75,7 +74,7 @@ router
*/ */
router router
.route('/:list_id') .route('/:list_id')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())
@ -91,10 +90,10 @@ router
additionalProperties: false, additionalProperties: false,
properties: { properties: {
list_id: { list_id: {
$ref: 'common#/properties/id' $ref: 'definitions#/definitions/id'
}, },
expand: { expand: {
$ref: 'common#/properties/expand' $ref: 'definitions#/definitions/expand'
} }
} }
}, { }, {
@ -120,7 +119,7 @@ router
* Update and existing access-list * Update and existing access-list
*/ */
.put((req, res, next) => { .put((req, res, next) => {
apiValidator(schema.getValidationSchema('/nginx/access-lists/{listID}', 'put'), 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);

View File

@ -1,10 +1,8 @@
const express = require('express'); const express = require('express');
const error = require('../../lib/error'); 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 apiValidator = require('../../lib/validator/api'); const apiValidator = require('../../../lib/validator/api');
const internalCertificate = require('../../internal/certificate');
const schema = require('../../schema');
let router = express.Router({ let router = express.Router({
caseSensitive: true, caseSensitive: true,
@ -17,7 +15,7 @@ let router = express.Router({
*/ */
router router
.route('/') .route('/')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())
@ -32,10 +30,10 @@ router
additionalProperties: false, additionalProperties: false,
properties: { properties: {
expand: { expand: {
$ref: 'common#/properties/expand' $ref: 'definitions#/definitions/expand'
}, },
query: { query: {
$ref: 'common#/properties/query' $ref: 'definitions#/definitions/query'
} }
} }
}, { }, {
@ -58,7 +56,7 @@ router
* Create a new certificate * Create a new certificate
*/ */
.post((req, res, next) => { .post((req, res, next) => {
apiValidator(schema.getValidationSchema('/nginx/certificates', 'post'), 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);
@ -77,7 +75,7 @@ router
*/ */
router router
.route('/test-http') .route('/test-http')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())
@ -88,11 +86,6 @@ router
* Test HTTP challenge for domains * Test HTTP challenge for domains
*/ */
.get((req, res, next) => { .get((req, res, next) => {
if (req.query.domains === undefined) {
next(new error.ValidationError('Domains are required as query parameters'));
return;
}
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)
@ -108,7 +101,7 @@ router
*/ */
router router
.route('/:certificate_id') .route('/:certificate_id')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())
@ -124,10 +117,10 @@ router
additionalProperties: false, additionalProperties: false,
properties: { properties: {
certificate_id: { certificate_id: {
$ref: 'common#/properties/id' $ref: 'definitions#/definitions/id'
}, },
expand: { expand: {
$ref: 'common#/properties/expand' $ref: 'definitions#/definitions/expand'
} }
} }
}, { }, {
@ -147,6 +140,24 @@ router
.catch(next); .catch(next);
}) })
/**
* PUT /api/nginx/certificates/123
*
* Update and existing certificate
*/
.put((req, res, next) => {
apiValidator({$ref: 'endpoints/certificates#/links/2/schema'}, req.body)
.then((payload) => {
payload.id = parseInt(req.params.certificate_id, 10);
return internalCertificate.update(res.locals.access, payload);
})
.then((result) => {
res.status(200)
.send(result);
})
.catch(next);
})
/** /**
* DELETE /api/nginx/certificates/123 * DELETE /api/nginx/certificates/123
* *
@ -168,7 +179,7 @@ router
*/ */
router router
.route('/:certificate_id/upload') .route('/:certificate_id/upload')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())
@ -202,7 +213,7 @@ router
*/ */
router router
.route('/:certificate_id/renew') .route('/:certificate_id/renew')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())
@ -259,7 +270,7 @@ router
*/ */
router router
.route('/validate') .route('/validate')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())

View File

@ -1,9 +1,8 @@
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 apiValidator = require('../../lib/validator/api'); const internalDeadHost = require('../../../internal/dead-host');
const internalDeadHost = require('../../internal/dead-host'); const apiValidator = require('../../../lib/validator/api');
const schema = require('../../schema');
let router = express.Router({ let router = express.Router({
caseSensitive: true, caseSensitive: true,
@ -16,7 +15,7 @@ let router = express.Router({
*/ */
router router
.route('/') .route('/')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())
@ -31,10 +30,10 @@ router
additionalProperties: false, additionalProperties: false,
properties: { properties: {
expand: { expand: {
$ref: 'common#/properties/expand' $ref: 'definitions#/definitions/expand'
}, },
query: { query: {
$ref: 'common#/properties/query' $ref: 'definitions#/definitions/query'
} }
} }
}, { }, {
@ -57,7 +56,7 @@ router
* Create a new dead-host * Create a new dead-host
*/ */
.post((req, res, next) => { .post((req, res, next) => {
apiValidator(schema.getValidationSchema('/nginx/dead-hosts', 'post'), 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);
}) })
@ -91,10 +90,10 @@ router
additionalProperties: false, additionalProperties: false,
properties: { properties: {
host_id: { host_id: {
$ref: 'common#/properties/id' $ref: 'definitions#/definitions/id'
}, },
expand: { expand: {
$ref: 'common#/properties/expand' $ref: 'definitions#/definitions/expand'
} }
} }
}, { }, {
@ -120,7 +119,7 @@ router
* Update and existing dead-host * Update and existing dead-host
*/ */
.put((req, res, next) => { .put((req, res, next) => {
apiValidator(schema.getValidationSchema('/nginx/dead-hosts/{hostID}', 'put'), 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);
@ -153,7 +152,7 @@ router
*/ */
router router
.route('/:host_id/enable') .route('/:host_id/enable')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())
@ -177,7 +176,7 @@ router
*/ */
router router
.route('/:host_id/disable') .route('/:host_id/disable')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())

View File

@ -1,9 +1,8 @@
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 apiValidator = require('../../lib/validator/api'); const internalProxyHost = require('../../../internal/proxy-host');
const internalProxyHost = require('../../internal/proxy-host'); const apiValidator = require('../../../lib/validator/api');
const schema = require('../../schema');
let router = express.Router({ let router = express.Router({
caseSensitive: true, caseSensitive: true,
@ -31,10 +30,10 @@ router
additionalProperties: false, additionalProperties: false,
properties: { properties: {
expand: { expand: {
$ref: 'common#/properties/expand' $ref: 'definitions#/definitions/expand'
}, },
query: { query: {
$ref: 'common#/properties/query' $ref: 'definitions#/definitions/query'
} }
} }
}, { }, {
@ -57,7 +56,7 @@ router
* Create a new proxy-host * Create a new proxy-host
*/ */
.post((req, res, next) => { .post((req, res, next) => {
apiValidator(schema.getValidationSchema('/nginx/proxy-hosts', 'post'), 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);
}) })
@ -91,10 +90,10 @@ router
additionalProperties: false, additionalProperties: false,
properties: { properties: {
host_id: { host_id: {
$ref: 'common#/properties/id' $ref: 'definitions#/definitions/id'
}, },
expand: { expand: {
$ref: 'common#/properties/expand' $ref: 'definitions#/definitions/expand'
} }
} }
}, { }, {
@ -120,7 +119,7 @@ router
* Update and existing proxy-host * Update and existing proxy-host
*/ */
.put((req, res, next) => { .put((req, res, next) => {
apiValidator(schema.getValidationSchema('/nginx/proxy-hosts/{hostID}', 'put'), 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);
@ -153,7 +152,7 @@ router
*/ */
router router
.route('/:host_id/enable') .route('/:host_id/enable')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())
@ -177,7 +176,7 @@ router
*/ */
router router
.route('/:host_id/disable') .route('/:host_id/disable')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())

View File

@ -1,9 +1,8 @@
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 apiValidator = require('../../lib/validator/api'); const internalRedirectionHost = require('../../../internal/redirection-host');
const internalRedirectionHost = require('../../internal/redirection-host'); const apiValidator = require('../../../lib/validator/api');
const schema = require('../../schema');
let router = express.Router({ let router = express.Router({
caseSensitive: true, caseSensitive: true,
@ -31,10 +30,10 @@ router
additionalProperties: false, additionalProperties: false,
properties: { properties: {
expand: { expand: {
$ref: 'common#/properties/expand' $ref: 'definitions#/definitions/expand'
}, },
query: { query: {
$ref: 'common#/properties/query' $ref: 'definitions#/definitions/query'
} }
} }
}, { }, {
@ -57,7 +56,7 @@ router
* Create a new redirection-host * Create a new redirection-host
*/ */
.post((req, res, next) => { .post((req, res, next) => {
apiValidator(schema.getValidationSchema('/nginx/redirection-hosts', 'post'), 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);
}) })
@ -91,10 +90,10 @@ router
additionalProperties: false, additionalProperties: false,
properties: { properties: {
host_id: { host_id: {
$ref: 'common#/properties/id' $ref: 'definitions#/definitions/id'
}, },
expand: { expand: {
$ref: 'common#/properties/expand' $ref: 'definitions#/definitions/expand'
} }
} }
}, { }, {
@ -120,7 +119,7 @@ router
* Update and existing redirection-host * Update and existing redirection-host
*/ */
.put((req, res, next) => { .put((req, res, next) => {
apiValidator(schema.getValidationSchema('/nginx/redirection-hosts/{hostID}', 'put'), 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);

View File

@ -1,9 +1,8 @@
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 apiValidator = require('../../lib/validator/api'); const internalStream = require('../../../internal/stream');
const internalStream = require('../../internal/stream'); const apiValidator = require('../../../lib/validator/api');
const schema = require('../../schema');
let router = express.Router({ let router = express.Router({
caseSensitive: true, caseSensitive: true,
@ -31,10 +30,10 @@ router
additionalProperties: false, additionalProperties: false,
properties: { properties: {
expand: { expand: {
$ref: 'common#/properties/expand' $ref: 'definitions#/definitions/expand'
}, },
query: { query: {
$ref: 'common#/properties/query' $ref: 'definitions#/definitions/query'
} }
} }
}, { }, {
@ -57,7 +56,7 @@ router
* Create a new stream * Create a new stream
*/ */
.post((req, res, next) => { .post((req, res, next) => {
apiValidator(schema.getValidationSchema('/nginx/streams', 'post'), 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);
}) })
@ -91,10 +90,10 @@ router
additionalProperties: false, additionalProperties: false,
properties: { properties: {
stream_id: { stream_id: {
$ref: 'common#/properties/id' $ref: 'definitions#/definitions/id'
}, },
expand: { expand: {
$ref: 'common#/properties/expand' $ref: 'definitions#/definitions/expand'
} }
} }
}, { }, {
@ -120,7 +119,7 @@ router
* Update and existing stream * Update and existing stream
*/ */
.put((req, res, next) => { .put((req, res, next) => {
apiValidator(schema.getValidationSchema('/nginx/streams/{streamID}', 'put'), 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);
@ -153,7 +152,7 @@ router
*/ */
router router
.route('/:host_id/enable') .route('/:host_id/enable')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())
@ -177,7 +176,7 @@ router
*/ */
router router
.route('/:host_id/disable') .route('/:host_id/disable')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())

View File

@ -1,6 +1,6 @@
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({ let router = express.Router({
caseSensitive: true, caseSensitive: true,
@ -10,14 +10,14 @@ let router = express.Router({
router router
.route('/hosts') .route('/hosts')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
/** /**
* GET /reports/hosts * GET /reports/hosts
*/ */
.get(jwtdecode(), (_, 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)

View File

@ -1,8 +1,8 @@
const express = require('express'); const express = require('express');
const schema = require('../schema'); const swaggerJSON = require('../../doc/api.swagger.json');
const PACKAGE = require('../package.json'); const PACKAGE = require('../../package.json');
const router = express.Router({ let router = express.Router({
caseSensitive: true, caseSensitive: true,
strict: true, strict: true,
mergeParams: true mergeParams: true
@ -10,16 +10,14 @@ const router = express.Router({
router router
.route('/') .route('/')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
/** /**
* GET /schema * GET /schema
*/ */
.get(async (req, res) => { .get((req, res/*, next*/) => {
let swaggerJSON = await schema.getCompiledSchema();
let proto = req.protocol; let proto = req.protocol;
if (typeof req.headers['x-forwarded-proto'] !== 'undefined' && req.headers['x-forwarded-proto']) { if (typeof req.headers['x-forwarded-proto'] !== 'undefined' && req.headers['x-forwarded-proto']) {
proto = req.headers['x-forwarded-proto']; proto = req.headers['x-forwarded-proto'];

View File

@ -1,9 +1,8 @@
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 apiValidator = require('../lib/validator/api'); const internalSetting = require('../../internal/setting');
const internalSetting = require('../internal/setting'); const apiValidator = require('../../lib/validator/api');
const schema = require('../schema');
let router = express.Router({ let router = express.Router({
caseSensitive: true, caseSensitive: true,
@ -16,7 +15,7 @@ let router = express.Router({
*/ */
router router
.route('/') .route('/')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())
@ -26,7 +25,7 @@ router
* *
* Retrieve all settings * Retrieve all settings
*/ */
.get((_, 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)
@ -42,7 +41,7 @@ router
*/ */
router router
.route('/:setting_id') .route('/:setting_id')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())
@ -58,8 +57,7 @@ router
additionalProperties: false, additionalProperties: false,
properties: { properties: {
setting_id: { setting_id: {
type: 'string', $ref: 'definitions#/definitions/setting_id'
minLength: 1
} }
} }
}, { }, {
@ -83,7 +81,7 @@ router
* Update and existing setting * Update and existing setting
*/ */
.put((req, res, next) => { .put((req, res, next) => {
apiValidator(schema.getValidationSchema('/settings/{settingID}', 'put'), 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);

View File

@ -1,8 +1,7 @@
const express = require('express'); const express = require('express');
const jwtdecode = require('../lib/express/jwt-decode'); const jwtdecode = require('../../lib/express/jwt-decode');
const apiValidator = require('../lib/validator/api'); const internalToken = require('../../internal/token');
const internalToken = require('../internal/token'); const apiValidator = require('../../lib/validator/api');
const schema = require('../schema');
let router = express.Router({ let router = express.Router({
caseSensitive: true, caseSensitive: true,
@ -12,7 +11,7 @@ let router = express.Router({
router router
.route('/') .route('/')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
@ -40,9 +39,11 @@ router
* *
* Create a new Token * Create a new Token
*/ */
.post(async (req, res, next) => { .post((req, res, next) => {
apiValidator(schema.getValidationSchema('/tokens', 'post'), req.body) apiValidator({$ref: 'endpoints/tokens#/links/0/schema'}, req.body)
.then(internalToken.getTokenFromEmail) .then((payload) => {
return internalToken.getTokenFromEmail(payload);
})
.then((data) => { .then((data) => {
res.status(200) res.status(200)
.send(data); .send(data);

View File

@ -1,10 +1,9 @@
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');
const schema = require('../schema');
let router = express.Router({ let router = express.Router({
caseSensitive: true, caseSensitive: true,
@ -17,7 +16,7 @@ let router = express.Router({
*/ */
router router
.route('/') .route('/')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())
@ -32,10 +31,10 @@ router
additionalProperties: false, additionalProperties: false,
properties: { properties: {
expand: { expand: {
$ref: 'common#/properties/expand' $ref: 'definitions#/definitions/expand'
}, },
query: { query: {
$ref: 'common#/properties/query' $ref: 'definitions#/definitions/query'
} }
} }
}, { }, {
@ -49,11 +48,7 @@ router
res.status(200) res.status(200)
.send(users); .send(users);
}) })
.catch((err) => { .catch(next);
console.log(err);
next(err);
});
//.catch(next);
}) })
/** /**
@ -62,7 +57,7 @@ router
* Create a new User * Create a new User
*/ */
.post((req, res, next) => { .post((req, res, next) => {
apiValidator(schema.getValidationSchema('/users', 'post'), 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);
}) })
@ -80,7 +75,7 @@ router
*/ */
router router
.route('/:user_id') .route('/:user_id')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())
@ -97,10 +92,10 @@ router
additionalProperties: false, additionalProperties: false,
properties: { properties: {
user_id: { user_id: {
$ref: 'common#/properties/id' $ref: 'definitions#/definitions/id'
}, },
expand: { expand: {
$ref: 'common#/properties/expand' $ref: 'definitions#/definitions/expand'
} }
} }
}, { }, {
@ -118,10 +113,7 @@ router
res.status(200) res.status(200)
.send(user); .send(user);
}) })
.catch((err) => { .catch(next);
console.log(err);
next(err);
});
}) })
/** /**
@ -130,7 +122,7 @@ router
* Update and existing user * Update and existing user
*/ */
.put((req, res, next) => { .put((req, res, next) => {
apiValidator(schema.getValidationSchema('/users/{userID}', 'put'), 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);
@ -175,7 +167,7 @@ router
* Update password for a user * Update password for a user
*/ */
.put((req, res, next) => { .put((req, res, next) => {
apiValidator(schema.getValidationSchema('/users/{userID}/auth', 'put'), 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);
@ -206,7 +198,7 @@ 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(schema.getValidationSchema('/users/{userID}/permissions', 'put'), 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);
@ -225,7 +217,7 @@ router
*/ */
router router
.route('/:user_id/login') .route('/:user_id/login')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())

View File

@ -1,115 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "common",
"type": "object",
"properties": {
"id": {
"description": "Unique identifier",
"readOnly": true,
"type": "integer",
"minimum": 1
},
"expand": {
"anyOf": [
{
"type": "null"
},
{
"type": "array",
"minItems": 1,
"items": {
"type": "string"
}
}
]
},
"query": {
"anyOf": [
{
"type": "null"
},
{
"type": "string",
"minLength": 1,
"maxLength": 255
}
]
},
"created_on": {
"description": "Date and time of creation",
"readOnly": true,
"type": "string"
},
"modified_on": {
"description": "Date and time of last update",
"readOnly": true,
"type": "string"
},
"user_id": {
"description": "User ID",
"type": "integer",
"minimum": 1
},
"certificate_id": {
"description": "Certificate ID",
"anyOf": [
{
"type": "integer",
"minimum": 0
},
{
"type": "string",
"pattern": "^new$"
}
]
},
"access_list_id": {
"description": "Access List ID",
"type": "integer",
"minimum": 0
},
"domain_names": {
"description": "Domain Names separated by a comma",
"type": "array",
"minItems": 1,
"maxItems": 100,
"uniqueItems": true,
"items": {
"type": "string",
"pattern": "^[^&| @!#%^();:/\\\\}{=+?<>,~`'\"]+$"
}
},
"enabled": {
"description": "Is Enabled",
"type": "boolean"
},
"ssl_forced": {
"description": "Is SSL Forced",
"type": "boolean"
},
"hsts_enabled": {
"description": "Is HSTS Enabled",
"type": "boolean"
},
"hsts_subdomains": {
"description": "Is HSTS applicable to all subdomains",
"type": "boolean"
},
"ssl_provider": {
"type": "string",
"pattern": "^(letsencrypt|other)$"
},
"http2_support": {
"description": "HTTP2 Protocol Support",
"type": "boolean"
},
"block_exploits": {
"description": "Should we block common exploits",
"type": "boolean"
},
"caching_enabled": {
"description": "Should we cache assets",
"type": "boolean"
}
}
}

View File

@ -1,53 +0,0 @@
{
"type": "object",
"description": "Access List object",
"required": ["id", "created_on", "modified_on", "owner_user_id", "name", "directive", "address", "satisfy_any", "pass_auth", "meta"],
"additionalProperties": false,
"properties": {
"id": {
"$ref": "../common.json#/properties/id"
},
"created_on": {
"$ref": "../common.json#/properties/created_on"
},
"modified_on": {
"$ref": "../common.json#/properties/modified_on"
},
"owner_user_id": {
"$ref": "../common.json#/properties/user_id"
},
"name": {
"type": "string",
"minLength": 1
},
"directive": {
"type": "string",
"enum": ["allow", "deny"]
},
"address": {
"oneOf": [
{
"type": "string",
"pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$"
},
{
"type": "string",
"pattern": "^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$"
},
{
"type": "string",
"pattern": "^all$"
}
]
},
"satisfy_any": {
"type": "boolean"
},
"pass_auth": {
"type": "boolean"
},
"meta": {
"type": "object"
}
}
}

View File

@ -1,32 +0,0 @@
{
"type": "object",
"description": "Audit Log object",
"required": ["id", "created_on", "modified_on", "user_id", "object_type", "object_id", "action", "meta"],
"additionalProperties": false,
"properties": {
"id": {
"$ref": "../common.json#/properties/id"
},
"created_on": {
"$ref": "../common.json#/properties/created_on"
},
"modified_on": {
"$ref": "../common.json#/properties/modified_on"
},
"user_id": {
"$ref": "../common.json#/properties/user_id"
},
"object_type": {
"type": "string"
},
"object_id": {
"$ref": "../common.json#/properties/id"
},
"action": {
"type": "string"
},
"meta": {
"type": "object"
}
}
}

View File

@ -1,7 +0,0 @@
{
"type": "array",
"description": "Certificates list",
"items": {
"$ref": "./certificate-object.json"
}
}

View File

@ -1,81 +0,0 @@
{
"type": "object",
"description": "Certificate object",
"required": ["id", "created_on", "modified_on", "owner_user_id", "provider", "nice_name", "domain_names", "expires_on", "meta"],
"additionalProperties": false,
"properties": {
"id": {
"$ref": "../common.json#/properties/id"
},
"created_on": {
"$ref": "../common.json#/properties/created_on"
},
"modified_on": {
"$ref": "../common.json#/properties/modified_on"
},
"owner_user_id": {
"$ref": "../common.json#/properties/user_id"
},
"provider": {
"$ref": "../common.json#/properties/ssl_provider"
},
"nice_name": {
"type": "string",
"description": "Nice Name for the custom certificate"
},
"domain_names": {
"description": "Domain Names separated by a comma",
"type": "array",
"maxItems": 100,
"uniqueItems": true,
"items": {
"type": "string",
"pattern": "^[^&| @!#%^();:/\\\\}{=+?<>,~`'\"]+$"
}
},
"expires_on": {
"description": "Date and time of expiration",
"readOnly": true,
"type": "string"
},
"owner": {
"$ref": "./user-object.json"
},
"meta": {
"type": "object",
"additionalProperties": false,
"properties": {
"certificate": {
"type": "string",
"minLength": 1
},
"certificate_key": {
"type": "string",
"minLength": 1
},
"dns_challenge": {
"type": "boolean"
},
"dns_provider": {
"type": "string"
},
"dns_provider_credentials": {
"type": "string"
},
"letsencrypt_agree": {
"type": "boolean"
},
"letsencrypt_certificate": {
"type": "object"
},
"letsencrypt_email": {
"type": "string"
},
"propagation_seconds": {
"type": "integer",
"minimum": 0
}
}
}
}
}

View File

@ -1,7 +0,0 @@
{
"type": "array",
"description": "404 Hosts list",
"items": {
"$ref": "./dead-host-object.json"
}
}

View File

@ -1,47 +0,0 @@
{
"type": "object",
"description": "404 Host object",
"required": ["id", "created_on", "modified_on", "owner_user_id", "domain_names", "certificate_id", "ssl_forced", "hsts_enabled", "hsts_subdomains", "http2_support", "advanced_config", "enabled", "meta"],
"additionalProperties": false,
"properties": {
"id": {
"$ref": "../common.json#/properties/id"
},
"created_on": {
"$ref": "../common.json#/properties/created_on"
},
"modified_on": {
"$ref": "../common.json#/properties/modified_on"
},
"owner_user_id": {
"$ref": "../common.json#/properties/user_id"
},
"domain_names": {
"$ref": "../common.json#/properties/domain_names"
},
"certificate_id": {
"$ref": "../common.json#/properties/certificate_id"
},
"ssl_forced": {
"$ref": "../common.json#/properties/ssl_forced"
},
"hsts_enabled": {
"$ref": "../common.json#/properties/hsts_enabled"
},
"hsts_subdomains": {
"$ref": "../common.json#/properties/hsts_subdomains"
},
"http2_support": {
"$ref": "../common.json#/properties/http2_support"
},
"advanced_config": {
"type": "string"
},
"enabled": {
"$ref": "../common.json#/properties/enabled"
},
"meta": {
"type": "object"
}
}
}

View File

@ -1,14 +0,0 @@
{
"type": "object",
"description": "Error object",
"additionalProperties": false,
"required": ["code", "message"],
"properties": {
"code": {
"type": "integer"
},
"message": {
"type": "string"
}
}
}

View File

@ -1,9 +0,0 @@
{
"type": "object",
"description": "Error",
"properties": {
"error": {
"$ref": "./error-object.json"
}
}
}

View File

@ -1,38 +0,0 @@
{
"type": "object",
"description": "Health object",
"additionalProperties": false,
"required": ["status", "version"],
"properties": {
"status": {
"type": "string",
"description": "Healthy",
"example": "OK"
},
"version": {
"type": "object",
"description": "The version object",
"example": {
"major": 2,
"minor": 0,
"revision": 0
},
"additionalProperties": false,
"required": ["major", "minor", "revision"],
"properties": {
"major": {
"type": "integer",
"minimum": 0
},
"minor": {
"type": "integer",
"minimum": 0
},
"revision": {
"type": "integer",
"minimum": 0
}
}
}
}
}

View File

@ -1,41 +0,0 @@
{
"type": "object",
"minProperties": 1,
"properties": {
"visibility": {
"type": "string",
"description": "Visibility Type",
"enum": ["all", "user"]
},
"access_lists": {
"type": "string",
"description": "Access Lists Permissions",
"enum": ["hidden", "view", "manage"]
},
"dead_hosts": {
"type": "string",
"description": "404 Hosts Permissions",
"enum": ["hidden", "view", "manage"]
},
"proxy_hosts": {
"type": "string",
"description": "Proxy Hosts Permissions",
"enum": ["hidden", "view", "manage"]
},
"redirection_hosts": {
"type": "string",
"description": "Redirection Permissions",
"enum": ["hidden", "view", "manage"]
},
"streams": {
"type": "string",
"description": "Streams Permissions",
"enum": ["hidden", "view", "manage"]
},
"certificates": {
"type": "string",
"description": "Certificates Permissions",
"enum": ["hidden", "view", "manage"]
}
}
}

View File

@ -1,7 +0,0 @@
{
"type": "array",
"description": "Proxy Hosts list",
"items": {
"$ref": "./proxy-host-object.json"
}
}

View File

@ -1,153 +0,0 @@
{
"type": "object",
"description": "Proxy Host object",
"required": [
"id",
"created_on",
"modified_on",
"owner_user_id",
"domain_names",
"forward_host",
"forward_port",
"access_list_id",
"certificate_id",
"ssl_forced",
"caching_enabled",
"block_exploits",
"advanced_config",
"meta",
"allow_websocket_upgrade",
"http2_support",
"forward_scheme",
"enabled",
"locations",
"hsts_enabled",
"hsts_subdomains"
],
"additionalProperties": false,
"properties": {
"id": {
"$ref": "../common.json#/properties/id"
},
"created_on": {
"$ref": "../common.json#/properties/created_on"
},
"modified_on": {
"$ref": "../common.json#/properties/modified_on"
},
"owner_user_id": {
"$ref": "../common.json#/properties/user_id"
},
"domain_names": {
"$ref": "../common.json#/properties/domain_names"
},
"forward_host": {
"type": "string",
"minLength": 1,
"maxLength": 255
},
"forward_port": {
"type": "integer",
"minimum": 1,
"maximum": 65535
},
"access_list_id": {
"$ref": "../common.json#/properties/access_list_id"
},
"certificate_id": {
"$ref": "../common.json#/properties/certificate_id"
},
"ssl_forced": {
"$ref": "../common.json#/properties/ssl_forced"
},
"caching_enabled": {
"$ref": "../common.json#/properties/caching_enabled"
},
"block_exploits": {
"$ref": "../common.json#/properties/block_exploits"
},
"advanced_config": {
"type": "string"
},
"meta": {
"type": "object"
},
"allow_websocket_upgrade": {
"description": "Allow Websocket Upgrade for all paths",
"example": true,
"type": "boolean"
},
"http2_support": {
"$ref": "../common.json#/properties/http2_support"
},
"forward_scheme": {
"type": "string",
"enum": ["http", "https"]
},
"enabled": {
"$ref": "../common.json#/properties/enabled"
},
"locations": {
"type": "array",
"minItems": 0,
"items": {
"type": "object",
"required": ["forward_scheme", "forward_host", "forward_port", "path"],
"additionalProperties": false,
"properties": {
"id": {
"type": ["integer", "null"]
},
"path": {
"type": "string",
"minLength": 1
},
"forward_scheme": {
"$ref": "#/properties/forward_scheme"
},
"forward_host": {
"$ref": "#/properties/forward_host"
},
"forward_port": {
"$ref": "#/properties/forward_port"
},
"forward_path": {
"type": "string"
},
"advanced_config": {
"type": "string"
}
}
}
},
"hsts_enabled": {
"$ref": "../common.json#/properties/hsts_enabled"
},
"hsts_subdomains": {
"$ref": "../common.json#/properties/hsts_subdomains"
},
"certificate": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "./certificate-object.json"
}
]
},
"owner": {
"$ref": "./user-object.json"
},
"access_list": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "./access-list-object.json"
}
]
}
}
}

View File

@ -1,7 +0,0 @@
{
"type": "array",
"description": "Redirection Hosts list",
"items": {
"$ref": "./redirection-host-object.json"
}
}

View File

@ -1,72 +0,0 @@
{
"type": "object",
"description": "Redirection Host object",
"required": ["id", "created_on", "modified_on", "owner_user_id", "domain_names", "forward_http_code", "forward_scheme", "forward_domain_name", "preserve_path", "certificate_id", "ssl_forced", "hsts_enabled", "hsts_subdomains", "http2_support", "block_exploits", "advanced_config", "enabled", "meta"],
"additionalProperties": false,
"properties": {
"id": {
"$ref": "../common.json#/properties/id"
},
"created_on": {
"$ref": "../common.json#/properties/created_on"
},
"modified_on": {
"$ref": "../common.json#/properties/modified_on"
},
"owner_user_id": {
"$ref": "../common.json#/properties/user_id"
},
"domain_names": {
"$ref": "../common.json#/properties/domain_names"
},
"forward_http_code": {
"description": "Redirect HTTP Status Code",
"example": 302,
"type": "integer",
"minimum": 300,
"maximum": 308
},
"forward_scheme": {
"type": "string",
"enum": ["auto", "http", "https"]
},
"forward_domain_name": {
"description": "Domain Name",
"example": "jc21.com",
"type": "string",
"pattern": "^(?:[^.*]+\\.?)+[^.]$"
},
"preserve_path": {
"description": "Should the path be preserved",
"example": true,
"type": "boolean"
},
"certificate_id": {
"$ref": "../common.json#/properties/certificate_id"
},
"ssl_forced": {
"$ref": "../common.json#/properties/ssl_forced"
},
"hsts_enabled": {
"$ref": "../common.json#/properties/hsts_enabled"
},
"hsts_subdomains": {
"$ref": "../common.json#/properties/hsts_subdomains"
},
"http2_support": {
"$ref": "../common.json#/properties/http2_support"
},
"block_exploits": {
"$ref": "../common.json#/properties/block_exploits"
},
"advanced_config": {
"type": "string"
},
"enabled": {
"$ref": "../common.json#/properties/enabled"
},
"meta": {
"type": "object"
}
}
}

View File

@ -1,6 +0,0 @@
{
"BearerAuth": {
"type": "http",
"scheme": "bearer"
}
}

View File

@ -1,7 +0,0 @@
{
"type": "array",
"description": "Setting list",
"items": {
"$ref": "./setting-object.json"
}
}

View File

@ -1,56 +0,0 @@
{
"type": "object",
"description": "Setting object",
"required": ["id", "name", "description", "value", "meta"],
"additionalProperties": false,
"properties": {
"id": {
"type": "string",
"description": "Setting ID",
"minLength": 1,
"example": "default-site"
},
"name": {
"type": "string",
"description": "Setting Display Name",
"minLength": 1,
"example": "Default Site"
},
"description": {
"type": "string",
"description": "Meaningful description",
"minLength": 1,
"example": "What to show when Nginx is hit with an unknown Host"
},
"value": {
"description": "Value in almost any form",
"example": "congratulations",
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "integer"
},
{
"type": "object"
},
{
"type": "number"
},
{
"type": "array"
}
]
},
"meta": {
"description": "Extra metadata",
"example": {
"redirect": "http://example.com",
"html": "<h1>404</h1>"
},
"type": "object"
}
}
}

View File

@ -1,7 +0,0 @@
{
"type": "array",
"description": "Proxy Hosts list",
"items": {
"$ref": "./proxy-host-object.json"
}
}

View File

@ -1,76 +0,0 @@
{
"type": "object",
"description": "Stream object",
"required": ["id", "created_on", "modified_on", "owner_user_id", "incoming_port", "forwarding_host", "forwarding_port", "tcp_forwarding", "udp_forwarding", "enabled", "meta"],
"additionalProperties": false,
"properties": {
"id": {
"$ref": "../common.json#/properties/id"
},
"created_on": {
"$ref": "../common.json#/properties/created_on"
},
"modified_on": {
"$ref": "../common.json#/properties/modified_on"
},
"owner_user_id": {
"$ref": "../common.json#/properties/user_id"
},
"incoming_port": {
"type": "integer",
"minimum": 1,
"maximum": 65535
},
"forwarding_host": {
"anyOf": [
{
"description": "Domain Name",
"example": "jc21.com",
"type": "string",
"pattern": "^(?:[^.*]+\\.?)+[^.]$"
},
{
"type": "string",
"format": "ipv4"
},
{
"type": "string",
"format": "ipv6"
}
]
},
"forwarding_port": {
"type": "integer",
"minimum": 1,
"maximum": 65535
},
"tcp_forwarding": {
"type": "boolean"
},
"udp_forwarding": {
"type": "boolean"
},
"enabled": {
"$ref": "../common.json#/properties/enabled"
},
"certificate_id": {
"$ref": "../common.json#/properties/certificate_id"
},
"meta": {
"type": "object"
},
"owner": {
"$ref": "./user-object.json"
},
"certificate": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "./certificate-object.json"
}
]
}
}
}

View File

@ -1,18 +0,0 @@
{
"type": "object",
"description": "Token object",
"required": ["expires", "token"],
"additionalProperties": false,
"properties": {
"expires": {
"description": "Token Expiry ISO Time String",
"example": "2025-02-04T20:40:46.340Z",
"type": "string"
},
"token": {
"description": "JWT Token",
"example": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4",
"type": "string"
}
}
}

View File

@ -1,7 +0,0 @@
{
"type": "array",
"description": "User list",
"items": {
"$ref": "./user-object.json"
}
}

View File

@ -1,59 +0,0 @@
{
"type": "object",
"description": "User object",
"required": ["id", "created_on", "modified_on", "is_disabled", "email", "name", "nickname", "avatar", "roles"],
"additionalProperties": false,
"properties": {
"id": {
"type": "integer",
"description": "User ID",
"minimum": 1,
"example": 1
},
"created_on": {
"type": "string",
"description": "Created Date",
"example": "2020-01-30T09:36:08.000Z"
},
"modified_on": {
"type": "string",
"description": "Modified Date",
"example": "2020-01-30T09:41:04.000Z"
},
"is_disabled": {
"type": "boolean",
"description": "Is user Disabled",
"example": true
},
"email": {
"type": "string",
"description": "Email",
"minLength": 3,
"example": "jc@jc21.com"
},
"name": {
"type": "string",
"description": "Name",
"minLength": 1,
"example": "Jamie Curnow"
},
"nickname": {
"type": "string",
"description": "Nickname",
"example": "James"
},
"avatar": {
"type": "string",
"description": "Gravatar URL based on email, without scheme",
"example": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm"
},
"roles": {
"description": "Roles applied",
"example": ["admin"],
"type": "array",
"items": {
"type": "string"
}
}
}
}

View File

@ -0,0 +1,240 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "definitions",
"definitions": {
"id": {
"description": "Unique identifier",
"example": 123456,
"readOnly": true,
"type": "integer",
"minimum": 1
},
"setting_id": {
"description": "Unique identifier for a Setting",
"example": "default-site",
"readOnly": true,
"type": "string",
"minLength": 2
},
"token": {
"type": "string",
"minLength": 10
},
"expand": {
"anyOf": [
{
"type": "null"
},
{
"type": "array",
"minItems": 1,
"items": {
"type": "string"
}
}
]
},
"sort": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": [
"field",
"dir"
],
"additionalProperties": false,
"properties": {
"field": {
"type": "string"
},
"dir": {
"type": "string",
"pattern": "^(asc|desc)$"
}
}
}
},
"query": {
"anyOf": [
{
"type": "null"
},
{
"type": "string",
"minLength": 1,
"maxLength": 255
}
]
},
"criteria": {
"anyOf": [
{
"type": "null"
},
{
"type": "object"
}
]
},
"fields": {
"anyOf": [
{
"type": "null"
},
{
"type": "array",
"minItems": 1,
"items": {
"type": "string"
}
}
]
},
"omit": {
"anyOf": [
{
"type": "null"
},
{
"type": "array",
"minItems": 1,
"items": {
"type": "string"
}
}
]
},
"created_on": {
"description": "Date and time of creation",
"format": "date-time",
"readOnly": true,
"type": "string"
},
"modified_on": {
"description": "Date and time of last update",
"format": "date-time",
"readOnly": true,
"type": "string"
},
"user_id": {
"description": "User ID",
"example": 1234,
"type": "integer",
"minimum": 1
},
"certificate_id": {
"description": "Certificate ID",
"example": 1234,
"anyOf": [
{
"type": "integer",
"minimum": 0
},
{
"type": "string",
"pattern": "^new$"
}
]
},
"access_list_id": {
"description": "Access List ID",
"example": 1234,
"type": "integer",
"minimum": 0
},
"name": {
"type": "string",
"minLength": 1,
"maxLength": 255
},
"email": {
"description": "Email Address",
"example": "john@example.com",
"format": "email",
"type": "string",
"minLength": 6,
"maxLength": 100
},
"password": {
"description": "Password",
"type": "string",
"minLength": 8,
"maxLength": 255
},
"domain_name": {
"description": "Domain Name",
"example": "jc21.com",
"type": "string",
"pattern": "^(?:[^.*]+\\.?)+[^.]$"
},
"domain_names": {
"description": "Domain Names separated by a comma",
"example": "*.jc21.com,blog.jc21.com",
"type": "array",
"maxItems": 15,
"uniqueItems": true,
"items": {
"type": "string",
"pattern": "^(?:\\*\\.)?(?:[^.*]+\\.?)+[^.]$"
}
},
"http_code": {
"description": "Redirect HTTP Status Code",
"example": 302,
"type": "integer",
"minimum": 300,
"maximum": 308
},
"scheme": {
"description": "RFC Protocol",
"example": "HTTPS or $scheme",
"type": "string",
"minLength": 4
},
"enabled": {
"description": "Is Enabled",
"example": true,
"type": "boolean"
},
"ssl_enabled": {
"description": "Is SSL Enabled",
"example": true,
"type": "boolean"
},
"ssl_forced": {
"description": "Is SSL Forced",
"example": false,
"type": "boolean"
},
"hsts_enabled": {
"description": "Is HSTS Enabled",
"example": false,
"type": "boolean"
},
"hsts_subdomains": {
"description": "Is HSTS applicable to all subdomains",
"example": false,
"type": "boolean"
},
"ssl_provider": {
"type": "string",
"pattern": "^(letsencrypt|other)$"
},
"http2_support": {
"description": "HTTP2 Protocol Support",
"example": false,
"type": "boolean"
},
"block_exploits": {
"description": "Should we block common exploits",
"example": true,
"type": "boolean"
},
"caching_enabled": {
"description": "Should we cache assets",
"example": true,
"type": "boolean"
}
}
}

View File

@ -0,0 +1,236 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "endpoints/access-lists",
"title": "Access Lists",
"description": "Endpoints relating to Access Lists",
"stability": "stable",
"type": "object",
"definitions": {
"id": {
"$ref": "../definitions.json#/definitions/id"
},
"created_on": {
"$ref": "../definitions.json#/definitions/created_on"
},
"modified_on": {
"$ref": "../definitions.json#/definitions/modified_on"
},
"name": {
"type": "string",
"description": "Name of the Access List"
},
"directive": {
"type": "string",
"enum": ["allow", "deny"]
},
"address": {
"oneOf": [
{
"type": "string",
"pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$"
},
{
"type": "string",
"pattern": "^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$"
},
{
"type": "string",
"pattern": "^all$"
}
]
},
"satisfy_any": {
"type": "boolean"
},
"pass_auth": {
"type": "boolean"
},
"meta": {
"type": "object"
}
},
"properties": {
"id": {
"$ref": "#/definitions/id"
},
"created_on": {
"$ref": "#/definitions/created_on"
},
"modified_on": {
"$ref": "#/definitions/modified_on"
},
"name": {
"$ref": "#/definitions/name"
},
"meta": {
"$ref": "#/definitions/meta"
}
},
"links": [
{
"title": "List",
"description": "Returns a list of Access Lists",
"href": "/nginx/access-lists",
"access": "private",
"method": "GET",
"rel": "self",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"targetSchema": {
"type": "array",
"items": {
"$ref": "#/properties"
}
}
},
{
"title": "Create",
"description": "Creates a new Access List",
"href": "/nginx/access-list",
"access": "private",
"method": "POST",
"rel": "create",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"schema": {
"type": "object",
"additionalProperties": false,
"required": ["name"],
"properties": {
"name": {
"$ref": "#/definitions/name"
},
"satisfy_any": {
"$ref": "#/definitions/satisfy_any"
},
"pass_auth": {
"$ref": "#/definitions/pass_auth"
},
"items": {
"type": "array",
"minItems": 0,
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"username": {
"type": "string",
"minLength": 1
},
"password": {
"type": "string",
"minLength": 1
}
}
}
},
"clients": {
"type": "array",
"minItems": 0,
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"address": {
"$ref": "#/definitions/address"
},
"directive": {
"$ref": "#/definitions/directive"
}
}
}
},
"meta": {
"$ref": "#/definitions/meta"
}
}
},
"targetSchema": {
"properties": {
"$ref": "#/properties"
}
}
},
{
"title": "Update",
"description": "Updates a existing Access List",
"href": "/nginx/access-list/{definitions.identity.example}",
"access": "private",
"method": "PUT",
"rel": "update",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"schema": {
"type": "object",
"additionalProperties": false,
"properties": {
"name": {
"$ref": "#/definitions/name"
},
"satisfy_any": {
"$ref": "#/definitions/satisfy_any"
},
"pass_auth": {
"$ref": "#/definitions/pass_auth"
},
"items": {
"type": "array",
"minItems": 0,
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"username": {
"type": "string",
"minLength": 1
},
"password": {
"type": "string",
"minLength": 0
}
}
}
},
"clients": {
"type": "array",
"minItems": 0,
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"address": {
"$ref": "#/definitions/address"
},
"directive": {
"$ref": "#/definitions/directive"
}
}
}
}
}
},
"targetSchema": {
"properties": {
"$ref": "#/properties"
}
}
},
{
"title": "Delete",
"description": "Deletes a existing Access List",
"href": "/nginx/access-list/{definitions.identity.example}",
"access": "private",
"method": "DELETE",
"rel": "delete",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"targetSchema": {
"type": "boolean"
}
}
]
}

View File

@ -0,0 +1,173 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "endpoints/certificates",
"title": "Certificates",
"description": "Endpoints relating to Certificates",
"stability": "stable",
"type": "object",
"definitions": {
"id": {
"$ref": "../definitions.json#/definitions/id"
},
"created_on": {
"$ref": "../definitions.json#/definitions/created_on"
},
"modified_on": {
"$ref": "../definitions.json#/definitions/modified_on"
},
"provider": {
"$ref": "../definitions.json#/definitions/ssl_provider"
},
"nice_name": {
"type": "string",
"description": "Nice Name for the custom certificate"
},
"domain_names": {
"$ref": "../definitions.json#/definitions/domain_names"
},
"expires_on": {
"description": "Date and time of expiration",
"format": "date-time",
"readOnly": true,
"type": "string"
},
"meta": {
"type": "object",
"additionalProperties": false,
"properties": {
"letsencrypt_email": {
"type": "string",
"format": "email"
},
"letsencrypt_agree": {
"type": "boolean"
},
"dns_challenge": {
"type": "boolean"
},
"dns_provider": {
"type": "string"
},
"dns_provider_credentials": {
"type": "string"
},
"propagation_seconds": {
"anyOf": [
{
"type": "integer",
"minimum": 0
}
]
}
}
}
},
"properties": {
"id": {
"$ref": "#/definitions/id"
},
"created_on": {
"$ref": "#/definitions/created_on"
},
"modified_on": {
"$ref": "#/definitions/modified_on"
},
"provider": {
"$ref": "#/definitions/provider"
},
"nice_name": {
"$ref": "#/definitions/nice_name"
},
"domain_names": {
"$ref": "#/definitions/domain_names"
},
"expires_on": {
"$ref": "#/definitions/expires_on"
},
"meta": {
"$ref": "#/definitions/meta"
}
},
"links": [
{
"title": "List",
"description": "Returns a list of Certificates",
"href": "/nginx/certificates",
"access": "private",
"method": "GET",
"rel": "self",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"targetSchema": {
"type": "array",
"items": {
"$ref": "#/properties"
}
}
},
{
"title": "Create",
"description": "Creates a new Certificate",
"href": "/nginx/certificates",
"access": "private",
"method": "POST",
"rel": "create",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"provider"
],
"properties": {
"provider": {
"$ref": "#/definitions/provider"
},
"nice_name": {
"$ref": "#/definitions/nice_name"
},
"domain_names": {
"$ref": "#/definitions/domain_names"
},
"meta": {
"$ref": "#/definitions/meta"
}
}
},
"targetSchema": {
"properties": {
"$ref": "#/properties"
}
}
},
{
"title": "Delete",
"description": "Deletes a existing Certificate",
"href": "/nginx/certificates/{definitions.identity.example}",
"access": "private",
"method": "DELETE",
"rel": "delete",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"targetSchema": {
"type": "boolean"
}
},
{
"title": "Test HTTP Challenge",
"description": "Tests whether the HTTP challenge should work",
"href": "/nginx/certificates/{definitions.identity.example}/test-http",
"access": "private",
"method": "GET",
"rel": "info",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
}
}
]
}

View File

@ -0,0 +1,240 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "endpoints/dead-hosts",
"title": "404 Hosts",
"description": "Endpoints relating to 404 Hosts",
"stability": "stable",
"type": "object",
"definitions": {
"id": {
"$ref": "../definitions.json#/definitions/id"
},
"created_on": {
"$ref": "../definitions.json#/definitions/created_on"
},
"modified_on": {
"$ref": "../definitions.json#/definitions/modified_on"
},
"domain_names": {
"$ref": "../definitions.json#/definitions/domain_names"
},
"certificate_id": {
"$ref": "../definitions.json#/definitions/certificate_id"
},
"ssl_forced": {
"$ref": "../definitions.json#/definitions/ssl_forced"
},
"hsts_enabled": {
"$ref": "../definitions.json#/definitions/hsts_enabled"
},
"hsts_subdomains": {
"$ref": "../definitions.json#/definitions/hsts_subdomains"
},
"http2_support": {
"$ref": "../definitions.json#/definitions/http2_support"
},
"advanced_config": {
"type": "string"
},
"enabled": {
"$ref": "../definitions.json#/definitions/enabled"
},
"meta": {
"type": "object"
}
},
"properties": {
"id": {
"$ref": "#/definitions/id"
},
"created_on": {
"$ref": "#/definitions/created_on"
},
"modified_on": {
"$ref": "#/definitions/modified_on"
},
"domain_names": {
"$ref": "#/definitions/domain_names"
},
"certificate_id": {
"$ref": "#/definitions/certificate_id"
},
"ssl_forced": {
"$ref": "#/definitions/ssl_forced"
},
"hsts_enabled": {
"$ref": "#/definitions/hsts_enabled"
},
"hsts_subdomains": {
"$ref": "#/definitions/hsts_subdomains"
},
"http2_support": {
"$ref": "#/definitions/http2_support"
},
"advanced_config": {
"$ref": "#/definitions/advanced_config"
},
"enabled": {
"$ref": "#/definitions/enabled"
},
"meta": {
"$ref": "#/definitions/meta"
}
},
"links": [
{
"title": "List",
"description": "Returns a list of 404 Hosts",
"href": "/nginx/dead-hosts",
"access": "private",
"method": "GET",
"rel": "self",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"targetSchema": {
"type": "array",
"items": {
"$ref": "#/properties"
}
}
},
{
"title": "Create",
"description": "Creates a new 404 Host",
"href": "/nginx/dead-hosts",
"access": "private",
"method": "POST",
"rel": "create",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"domain_names"
],
"properties": {
"domain_names": {
"$ref": "#/definitions/domain_names"
},
"certificate_id": {
"$ref": "#/definitions/certificate_id"
},
"ssl_forced": {
"$ref": "#/definitions/ssl_forced"
},
"hsts_enabled": {
"$ref": "#/definitions/hsts_enabled"
},
"hsts_subdomains": {
"$ref": "#/definitions/hsts_enabled"
},
"http2_support": {
"$ref": "#/definitions/http2_support"
},
"advanced_config": {
"$ref": "#/definitions/advanced_config"
},
"meta": {
"$ref": "#/definitions/meta"
}
}
},
"targetSchema": {
"properties": {
"$ref": "#/properties"
}
}
},
{
"title": "Update",
"description": "Updates a existing 404 Host",
"href": "/nginx/dead-hosts/{definitions.identity.example}",
"access": "private",
"method": "PUT",
"rel": "update",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"schema": {
"type": "object",
"additionalProperties": false,
"properties": {
"domain_names": {
"$ref": "#/definitions/domain_names"
},
"certificate_id": {
"$ref": "#/definitions/certificate_id"
},
"ssl_forced": {
"$ref": "#/definitions/ssl_forced"
},
"hsts_enabled": {
"$ref": "#/definitions/hsts_enabled"
},
"hsts_subdomains": {
"$ref": "#/definitions/hsts_enabled"
},
"http2_support": {
"$ref": "#/definitions/http2_support"
},
"advanced_config": {
"$ref": "#/definitions/advanced_config"
},
"meta": {
"$ref": "#/definitions/meta"
}
}
},
"targetSchema": {
"properties": {
"$ref": "#/properties"
}
}
},
{
"title": "Delete",
"description": "Deletes a existing 404 Host",
"href": "/nginx/dead-hosts/{definitions.identity.example}",
"access": "private",
"method": "DELETE",
"rel": "delete",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"targetSchema": {
"type": "boolean"
}
},
{
"title": "Enable",
"description": "Enables a existing 404 Host",
"href": "/nginx/dead-hosts/{definitions.identity.example}/enable",
"access": "private",
"method": "POST",
"rel": "update",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"targetSchema": {
"type": "boolean"
}
},
{
"title": "Disable",
"description": "Disables a existing 404 Host",
"href": "/nginx/dead-hosts/{definitions.identity.example}/disable",
"access": "private",
"method": "POST",
"rel": "update",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"targetSchema": {
"type": "boolean"
}
}
]
}

View File

@ -0,0 +1,387 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "endpoints/proxy-hosts",
"title": "Proxy Hosts",
"description": "Endpoints relating to Proxy Hosts",
"stability": "stable",
"type": "object",
"definitions": {
"id": {
"$ref": "../definitions.json#/definitions/id"
},
"created_on": {
"$ref": "../definitions.json#/definitions/created_on"
},
"modified_on": {
"$ref": "../definitions.json#/definitions/modified_on"
},
"domain_names": {
"$ref": "../definitions.json#/definitions/domain_names"
},
"forward_scheme": {
"type": "string",
"enum": ["http", "https"]
},
"forward_host": {
"type": "string",
"minLength": 1,
"maxLength": 255
},
"forward_port": {
"type": "integer",
"minimum": 1,
"maximum": 65535
},
"certificate_id": {
"$ref": "../definitions.json#/definitions/certificate_id"
},
"ssl_forced": {
"$ref": "../definitions.json#/definitions/ssl_forced"
},
"hsts_enabled": {
"$ref": "../definitions.json#/definitions/hsts_enabled"
},
"hsts_subdomains": {
"$ref": "../definitions.json#/definitions/hsts_subdomains"
},
"http2_support": {
"$ref": "../definitions.json#/definitions/http2_support"
},
"block_exploits": {
"$ref": "../definitions.json#/definitions/block_exploits"
},
"caching_enabled": {
"$ref": "../definitions.json#/definitions/caching_enabled"
},
"allow_websocket_upgrade": {
"description": "Allow Websocket Upgrade for all paths",
"example": true,
"type": "boolean"
},
"access_list_id": {
"$ref": "../definitions.json#/definitions/access_list_id"
},
"advanced_config": {
"type": "string"
},
"enabled": {
"$ref": "../definitions.json#/definitions/enabled"
},
"meta": {
"type": "object"
},
"locations": {
"type": "array",
"minItems": 0,
"items": {
"type": "object",
"required": [
"forward_scheme",
"forward_host",
"forward_port",
"path"
],
"additionalProperties": false,
"properties": {
"id": {
"type": ["integer", "null"]
},
"path": {
"type": "string",
"minLength": 1
},
"forward_scheme": {
"$ref": "#/definitions/forward_scheme"
},
"forward_host": {
"$ref": "#/definitions/forward_host"
},
"forward_port": {
"$ref": "#/definitions/forward_port"
},
"forward_path": {
"type": "string"
},
"advanced_config": {
"type": "string"
}
}
}
}
},
"properties": {
"id": {
"$ref": "#/definitions/id"
},
"created_on": {
"$ref": "#/definitions/created_on"
},
"modified_on": {
"$ref": "#/definitions/modified_on"
},
"domain_names": {
"$ref": "#/definitions/domain_names"
},
"forward_scheme": {
"$ref": "#/definitions/forward_scheme"
},
"forward_host": {
"$ref": "#/definitions/forward_host"
},
"forward_port": {
"$ref": "#/definitions/forward_port"
},
"certificate_id": {
"$ref": "#/definitions/certificate_id"
},
"ssl_forced": {
"$ref": "#/definitions/ssl_forced"
},
"hsts_enabled": {
"$ref": "#/definitions/hsts_enabled"
},
"hsts_subdomains": {
"$ref": "#/definitions/hsts_subdomains"
},
"http2_support": {
"$ref": "#/definitions/http2_support"
},
"block_exploits": {
"$ref": "#/definitions/block_exploits"
},
"caching_enabled": {
"$ref": "#/definitions/caching_enabled"
},
"allow_websocket_upgrade": {
"$ref": "#/definitions/allow_websocket_upgrade"
},
"access_list_id": {
"$ref": "#/definitions/access_list_id"
},
"advanced_config": {
"$ref": "#/definitions/advanced_config"
},
"enabled": {
"$ref": "#/definitions/enabled"
},
"meta": {
"$ref": "#/definitions/meta"
},
"locations": {
"$ref": "#/definitions/locations"
}
},
"links": [
{
"title": "List",
"description": "Returns a list of Proxy Hosts",
"href": "/nginx/proxy-hosts",
"access": "private",
"method": "GET",
"rel": "self",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"targetSchema": {
"type": "array",
"items": {
"$ref": "#/properties"
}
}
},
{
"title": "Create",
"description": "Creates a new Proxy Host",
"href": "/nginx/proxy-hosts",
"access": "private",
"method": "POST",
"rel": "create",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"domain_names",
"forward_scheme",
"forward_host",
"forward_port"
],
"properties": {
"domain_names": {
"$ref": "#/definitions/domain_names"
},
"forward_scheme": {
"$ref": "#/definitions/forward_scheme"
},
"forward_host": {
"$ref": "#/definitions/forward_host"
},
"forward_port": {
"$ref": "#/definitions/forward_port"
},
"certificate_id": {
"$ref": "#/definitions/certificate_id"
},
"ssl_forced": {
"$ref": "#/definitions/ssl_forced"
},
"hsts_enabled": {
"$ref": "#/definitions/hsts_enabled"
},
"hsts_subdomains": {
"$ref": "#/definitions/hsts_enabled"
},
"http2_support": {
"$ref": "#/definitions/http2_support"
},
"block_exploits": {
"$ref": "#/definitions/block_exploits"
},
"caching_enabled": {
"$ref": "#/definitions/caching_enabled"
},
"allow_websocket_upgrade": {
"$ref": "#/definitions/allow_websocket_upgrade"
},
"access_list_id": {
"$ref": "#/definitions/access_list_id"
},
"advanced_config": {
"$ref": "#/definitions/advanced_config"
},
"enabled": {
"$ref": "#/definitions/enabled"
},
"meta": {
"$ref": "#/definitions/meta"
},
"locations": {
"$ref": "#/definitions/locations"
}
}
},
"targetSchema": {
"properties": {
"$ref": "#/properties"
}
}
},
{
"title": "Update",
"description": "Updates a existing Proxy Host",
"href": "/nginx/proxy-hosts/{definitions.identity.example}",
"access": "private",
"method": "PUT",
"rel": "update",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"schema": {
"type": "object",
"additionalProperties": false,
"properties": {
"domain_names": {
"$ref": "#/definitions/domain_names"
},
"forward_scheme": {
"$ref": "#/definitions/forward_scheme"
},
"forward_host": {
"$ref": "#/definitions/forward_host"
},
"forward_port": {
"$ref": "#/definitions/forward_port"
},
"certificate_id": {
"$ref": "#/definitions/certificate_id"
},
"ssl_forced": {
"$ref": "#/definitions/ssl_forced"
},
"hsts_enabled": {
"$ref": "#/definitions/hsts_enabled"
},
"hsts_subdomains": {
"$ref": "#/definitions/hsts_enabled"
},
"http2_support": {
"$ref": "#/definitions/http2_support"
},
"block_exploits": {
"$ref": "#/definitions/block_exploits"
},
"caching_enabled": {
"$ref": "#/definitions/caching_enabled"
},
"allow_websocket_upgrade": {
"$ref": "#/definitions/allow_websocket_upgrade"
},
"access_list_id": {
"$ref": "#/definitions/access_list_id"
},
"advanced_config": {
"$ref": "#/definitions/advanced_config"
},
"enabled": {
"$ref": "#/definitions/enabled"
},
"meta": {
"$ref": "#/definitions/meta"
},
"locations": {
"$ref": "#/definitions/locations"
}
}
},
"targetSchema": {
"properties": {
"$ref": "#/properties"
}
}
},
{
"title": "Delete",
"description": "Deletes a existing Proxy Host",
"href": "/nginx/proxy-hosts/{definitions.identity.example}",
"access": "private",
"method": "DELETE",
"rel": "delete",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"targetSchema": {
"type": "boolean"
}
},
{
"title": "Enable",
"description": "Enables a existing Proxy Host",
"href": "/nginx/proxy-hosts/{definitions.identity.example}/enable",
"access": "private",
"method": "POST",
"rel": "update",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"targetSchema": {
"type": "boolean"
}
},
{
"title": "Disable",
"description": "Disables a existing Proxy Host",
"href": "/nginx/proxy-hosts/{definitions.identity.example}/disable",
"access": "private",
"method": "POST",
"rel": "update",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"targetSchema": {
"type": "boolean"
}
}
]
}

View File

@ -0,0 +1,305 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "endpoints/redirection-hosts",
"title": "Redirection Hosts",
"description": "Endpoints relating to Redirection Hosts",
"stability": "stable",
"type": "object",
"definitions": {
"id": {
"$ref": "../definitions.json#/definitions/id"
},
"created_on": {
"$ref": "../definitions.json#/definitions/created_on"
},
"modified_on": {
"$ref": "../definitions.json#/definitions/modified_on"
},
"domain_names": {
"$ref": "../definitions.json#/definitions/domain_names"
},
"forward_http_code": {
"$ref": "../definitions.json#/definitions/http_code"
},
"forward_scheme": {
"$ref": "../definitions.json#/definitions/scheme"
},
"forward_domain_name": {
"$ref": "../definitions.json#/definitions/domain_name"
},
"preserve_path": {
"description": "Should the path be preserved",
"example": true,
"type": "boolean"
},
"certificate_id": {
"$ref": "../definitions.json#/definitions/certificate_id"
},
"ssl_forced": {
"$ref": "../definitions.json#/definitions/ssl_forced"
},
"hsts_enabled": {
"$ref": "../definitions.json#/definitions/hsts_enabled"
},
"hsts_subdomains": {
"$ref": "../definitions.json#/definitions/hsts_subdomains"
},
"http2_support": {
"$ref": "../definitions.json#/definitions/http2_support"
},
"block_exploits": {
"$ref": "../definitions.json#/definitions/block_exploits"
},
"advanced_config": {
"type": "string"
},
"enabled": {
"$ref": "../definitions.json#/definitions/enabled"
},
"meta": {
"type": "object"
}
},
"properties": {
"id": {
"$ref": "#/definitions/id"
},
"created_on": {
"$ref": "#/definitions/created_on"
},
"modified_on": {
"$ref": "#/definitions/modified_on"
},
"domain_names": {
"$ref": "#/definitions/domain_names"
},
"forward_http_code": {
"$ref": "#/definitions/forward_http_code"
},
"forward_scheme": {
"$ref": "#/definitions/forward_scheme"
},
"forward_domain_name": {
"$ref": "#/definitions/forward_domain_name"
},
"preserve_path": {
"$ref": "#/definitions/preserve_path"
},
"certificate_id": {
"$ref": "#/definitions/certificate_id"
},
"ssl_forced": {
"$ref": "#/definitions/ssl_forced"
},
"hsts_enabled": {
"$ref": "#/definitions/hsts_enabled"
},
"hsts_subdomains": {
"$ref": "#/definitions/hsts_subdomains"
},
"http2_support": {
"$ref": "#/definitions/http2_support"
},
"block_exploits": {
"$ref": "#/definitions/block_exploits"
},
"advanced_config": {
"$ref": "#/definitions/advanced_config"
},
"enabled": {
"$ref": "#/definitions/enabled"
},
"meta": {
"$ref": "#/definitions/meta"
}
},
"links": [
{
"title": "List",
"description": "Returns a list of Redirection Hosts",
"href": "/nginx/redirection-hosts",
"access": "private",
"method": "GET",
"rel": "self",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"targetSchema": {
"type": "array",
"items": {
"$ref": "#/properties"
}
}
},
{
"title": "Create",
"description": "Creates a new Redirection Host",
"href": "/nginx/redirection-hosts",
"access": "private",
"method": "POST",
"rel": "create",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"domain_names",
"forward_scheme",
"forward_http_code",
"forward_domain_name"
],
"properties": {
"domain_names": {
"$ref": "#/definitions/domain_names"
},
"forward_http_code": {
"$ref": "#/definitions/forward_http_code"
},
"forward_scheme": {
"$ref": "#/definitions/forward_scheme"
},
"forward_domain_name": {
"$ref": "#/definitions/forward_domain_name"
},
"preserve_path": {
"$ref": "#/definitions/preserve_path"
},
"certificate_id": {
"$ref": "#/definitions/certificate_id"
},
"ssl_forced": {
"$ref": "#/definitions/ssl_forced"
},
"hsts_enabled": {
"$ref": "#/definitions/hsts_enabled"
},
"hsts_subdomains": {
"$ref": "#/definitions/hsts_enabled"
},
"http2_support": {
"$ref": "#/definitions/http2_support"
},
"block_exploits": {
"$ref": "#/definitions/block_exploits"
},
"advanced_config": {
"$ref": "#/definitions/advanced_config"
},
"meta": {
"$ref": "#/definitions/meta"
}
}
},
"targetSchema": {
"properties": {
"$ref": "#/properties"
}
}
},
{
"title": "Update",
"description": "Updates a existing Redirection Host",
"href": "/nginx/redirection-hosts/{definitions.identity.example}",
"access": "private",
"method": "PUT",
"rel": "update",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"schema": {
"type": "object",
"additionalProperties": false,
"properties": {
"domain_names": {
"$ref": "#/definitions/domain_names"
},
"forward_http_code": {
"$ref": "#/definitions/forward_http_code"
},
"forward_scheme": {
"$ref": "#/definitions/forward_scheme"
},
"forward_domain_name": {
"$ref": "#/definitions/forward_domain_name"
},
"preserve_path": {
"$ref": "#/definitions/preserve_path"
},
"certificate_id": {
"$ref": "#/definitions/certificate_id"
},
"ssl_forced": {
"$ref": "#/definitions/ssl_forced"
},
"hsts_enabled": {
"$ref": "#/definitions/hsts_enabled"
},
"hsts_subdomains": {
"$ref": "#/definitions/hsts_enabled"
},
"http2_support": {
"$ref": "#/definitions/http2_support"
},
"block_exploits": {
"$ref": "#/definitions/block_exploits"
},
"advanced_config": {
"$ref": "#/definitions/advanced_config"
},
"meta": {
"$ref": "#/definitions/meta"
}
}
},
"targetSchema": {
"properties": {
"$ref": "#/properties"
}
}
},
{
"title": "Delete",
"description": "Deletes a existing Redirection Host",
"href": "/nginx/redirection-hosts/{definitions.identity.example}",
"access": "private",
"method": "DELETE",
"rel": "delete",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"targetSchema": {
"type": "boolean"
}
},
{
"title": "Enable",
"description": "Enables a existing Redirection Host",
"href": "/nginx/redirection-hosts/{definitions.identity.example}/enable",
"access": "private",
"method": "POST",
"rel": "update",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"targetSchema": {
"type": "boolean"
}
},
{
"title": "Disable",
"description": "Disables a existing Redirection Host",
"href": "/nginx/redirection-hosts/{definitions.identity.example}/disable",
"access": "private",
"method": "POST",
"rel": "update",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"targetSchema": {
"type": "boolean"
}
}
]
}

View File

@ -0,0 +1,99 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "endpoints/settings",
"title": "Settings",
"description": "Endpoints relating to Settings",
"stability": "stable",
"type": "object",
"definitions": {
"id": {
"$ref": "../definitions.json#/definitions/setting_id"
},
"name": {
"description": "Name",
"example": "Default Site",
"type": "string",
"minLength": 2,
"maxLength": 100
},
"description": {
"description": "Description",
"example": "Default Site",
"type": "string",
"minLength": 2,
"maxLength": 255
},
"value": {
"description": "Value",
"example": "404",
"type": "string",
"maxLength": 255
},
"meta": {
"type": "object"
}
},
"links": [
{
"title": "List",
"description": "Returns a list of Settings",
"href": "/settings",
"access": "private",
"method": "GET",
"rel": "self",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"targetSchema": {
"type": "array",
"items": {
"$ref": "#/properties"
}
}
},
{
"title": "Update",
"description": "Updates a existing Setting",
"href": "/settings/{definitions.identity.example}",
"access": "private",
"method": "PUT",
"rel": "update",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"schema": {
"type": "object",
"properties": {
"value": {
"$ref": "#/definitions/value"
},
"meta": {
"$ref": "#/definitions/meta"
}
}
},
"targetSchema": {
"properties": {
"$ref": "#/properties"
}
}
}
],
"properties": {
"id": {
"$ref": "#/definitions/id"
},
"name": {
"$ref": "#/definitions/description"
},
"description": {
"$ref": "#/definitions/description"
},
"value": {
"$ref": "#/definitions/value"
},
"meta": {
"$ref": "#/definitions/meta"
}
}
}

View File

@ -0,0 +1,234 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "endpoints/streams",
"title": "Streams",
"description": "Endpoints relating to Streams",
"stability": "stable",
"type": "object",
"definitions": {
"id": {
"$ref": "../definitions.json#/definitions/id"
},
"created_on": {
"$ref": "../definitions.json#/definitions/created_on"
},
"modified_on": {
"$ref": "../definitions.json#/definitions/modified_on"
},
"incoming_port": {
"type": "integer",
"minimum": 1,
"maximum": 65535
},
"forwarding_host": {
"anyOf": [
{
"$ref": "../definitions.json#/definitions/domain_name"
},
{
"type": "string",
"format": "ipv4"
},
{
"type": "string",
"format": "ipv6"
}
]
},
"forwarding_port": {
"type": "integer",
"minimum": 1,
"maximum": 65535
},
"tcp_forwarding": {
"type": "boolean"
},
"udp_forwarding": {
"type": "boolean"
},
"enabled": {
"$ref": "../definitions.json#/definitions/enabled"
},
"meta": {
"type": "object"
}
},
"properties": {
"id": {
"$ref": "#/definitions/id"
},
"created_on": {
"$ref": "#/definitions/created_on"
},
"modified_on": {
"$ref": "#/definitions/modified_on"
},
"incoming_port": {
"$ref": "#/definitions/incoming_port"
},
"forwarding_host": {
"$ref": "#/definitions/forwarding_host"
},
"forwarding_port": {
"$ref": "#/definitions/forwarding_port"
},
"tcp_forwarding": {
"$ref": "#/definitions/tcp_forwarding"
},
"udp_forwarding": {
"$ref": "#/definitions/udp_forwarding"
},
"enabled": {
"$ref": "#/definitions/enabled"
},
"meta": {
"$ref": "#/definitions/meta"
}
},
"links": [
{
"title": "List",
"description": "Returns a list of Steams",
"href": "/nginx/streams",
"access": "private",
"method": "GET",
"rel": "self",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"targetSchema": {
"type": "array",
"items": {
"$ref": "#/properties"
}
}
},
{
"title": "Create",
"description": "Creates a new Stream",
"href": "/nginx/streams",
"access": "private",
"method": "POST",
"rel": "create",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"incoming_port",
"forwarding_host",
"forwarding_port"
],
"properties": {
"incoming_port": {
"$ref": "#/definitions/incoming_port"
},
"forwarding_host": {
"$ref": "#/definitions/forwarding_host"
},
"forwarding_port": {
"$ref": "#/definitions/forwarding_port"
},
"tcp_forwarding": {
"$ref": "#/definitions/tcp_forwarding"
},
"udp_forwarding": {
"$ref": "#/definitions/udp_forwarding"
},
"meta": {
"$ref": "#/definitions/meta"
}
}
},
"targetSchema": {
"properties": {
"$ref": "#/properties"
}
}
},
{
"title": "Update",
"description": "Updates a existing Stream",
"href": "/nginx/streams/{definitions.identity.example}",
"access": "private",
"method": "PUT",
"rel": "update",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"schema": {
"type": "object",
"additionalProperties": false,
"properties": {
"incoming_port": {
"$ref": "#/definitions/incoming_port"
},
"forwarding_host": {
"$ref": "#/definitions/forwarding_host"
},
"forwarding_port": {
"$ref": "#/definitions/forwarding_port"
},
"tcp_forwarding": {
"$ref": "#/definitions/tcp_forwarding"
},
"udp_forwarding": {
"$ref": "#/definitions/udp_forwarding"
},
"meta": {
"$ref": "#/definitions/meta"
}
}
},
"targetSchema": {
"properties": {
"$ref": "#/properties"
}
}
},
{
"title": "Delete",
"description": "Deletes a existing Stream",
"href": "/nginx/streams/{definitions.identity.example}",
"access": "private",
"method": "DELETE",
"rel": "delete",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"targetSchema": {
"type": "boolean"
}
},
{
"title": "Enable",
"description": "Enables a existing Stream",
"href": "/nginx/streams/{definitions.identity.example}/enable",
"access": "private",
"method": "POST",
"rel": "update",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"targetSchema": {
"type": "boolean"
}
},
{
"title": "Disable",
"description": "Disables a existing Stream",
"href": "/nginx/streams/{definitions.identity.example}/disable",
"access": "private",
"method": "POST",
"rel": "update",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"targetSchema": {
"type": "boolean"
}
}
]
}

View File

@ -0,0 +1,100 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "endpoints/tokens",
"title": "Token",
"description": "Tokens are required to authenticate against the API",
"stability": "stable",
"type": "object",
"definitions": {
"identity": {
"description": "Email Address or other 3rd party providers identifier",
"example": "john@example.com",
"type": "string"
},
"secret": {
"description": "A password or key",
"example": "correct horse battery staple",
"type": "string"
},
"token": {
"description": "JWT",
"example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.O_frfYM8RzmRsUNigHtu0_jZ_utSejyr1axMGa8rlsk",
"type": "string"
},
"expires": {
"description": "Token expiry time",
"format": "date-time",
"type": "string"
},
"scope": {
"description": "Scope of the Token, defaults to 'user'",
"example": "user",
"type": "string"
}
},
"links": [
{
"title": "Create",
"description": "Creates a new token.",
"href": "/tokens",
"access": "public",
"method": "POST",
"rel": "create",
"schema": {
"type": "object",
"required": [
"identity",
"secret"
],
"properties": {
"identity": {
"$ref": "#/definitions/identity"
},
"secret": {
"$ref": "#/definitions/secret"
},
"scope": {
"$ref": "#/definitions/scope"
}
}
},
"targetSchema": {
"type": "object",
"properties": {
"token": {
"$ref": "#/definitions/token"
},
"expires": {
"$ref": "#/definitions/expires"
}
}
}
},
{
"title": "Refresh",
"description": "Returns a new token.",
"href": "/tokens",
"access": "private",
"method": "GET",
"rel": "self",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"schema": {},
"targetSchema": {
"type": "object",
"properties": {
"token": {
"$ref": "#/definitions/token"
},
"expires": {
"$ref": "#/definitions/expires"
},
"scope": {
"$ref": "#/definitions/scope"
}
}
}
}
]
}

View File

@ -0,0 +1,287 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "endpoints/users",
"title": "Users",
"description": "Endpoints relating to Users",
"stability": "stable",
"type": "object",
"definitions": {
"id": {
"$ref": "../definitions.json#/definitions/id"
},
"created_on": {
"$ref": "../definitions.json#/definitions/created_on"
},
"modified_on": {
"$ref": "../definitions.json#/definitions/modified_on"
},
"name": {
"description": "Name",
"example": "Jamie Curnow",
"type": "string",
"minLength": 2,
"maxLength": 100
},
"nickname": {
"description": "Nickname",
"example": "Jamie",
"type": "string",
"minLength": 2,
"maxLength": 50
},
"email": {
"$ref": "../definitions.json#/definitions/email"
},
"avatar": {
"description": "Avatar",
"example": "http://somewhere.jpg",
"type": "string",
"minLength": 2,
"maxLength": 150,
"readOnly": true
},
"roles": {
"description": "Roles",
"example": [
"admin"
],
"type": "array"
},
"is_disabled": {
"description": "Is Disabled",
"example": false,
"type": "boolean"
}
},
"links": [
{
"title": "List",
"description": "Returns a list of Users",
"href": "/users",
"access": "private",
"method": "GET",
"rel": "self",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"targetSchema": {
"type": "array",
"items": {
"$ref": "#/properties"
}
}
},
{
"title": "Create",
"description": "Creates a new User",
"href": "/users",
"access": "private",
"method": "POST",
"rel": "create",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"schema": {
"type": "object",
"required": [
"name",
"nickname",
"email"
],
"properties": {
"name": {
"$ref": "#/definitions/name"
},
"nickname": {
"$ref": "#/definitions/nickname"
},
"email": {
"$ref": "#/definitions/email"
},
"roles": {
"$ref": "#/definitions/roles"
},
"is_disabled": {
"$ref": "#/definitions/is_disabled"
},
"auth": {
"type": "object",
"description": "Auth Credentials",
"example": {
"type": "password",
"secret": "bigredhorsebanana"
}
}
}
},
"targetSchema": {
"properties": {
"$ref": "#/properties"
}
}
},
{
"title": "Update",
"description": "Updates a existing User",
"href": "/users/{definitions.identity.example}",
"access": "private",
"method": "PUT",
"rel": "update",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"schema": {
"type": "object",
"properties": {
"name": {
"$ref": "#/definitions/name"
},
"nickname": {
"$ref": "#/definitions/nickname"
},
"email": {
"$ref": "#/definitions/email"
},
"roles": {
"$ref": "#/definitions/roles"
},
"is_disabled": {
"$ref": "#/definitions/is_disabled"
}
}
},
"targetSchema": {
"properties": {
"$ref": "#/properties"
}
}
},
{
"title": "Delete",
"description": "Deletes a existing User",
"href": "/users/{definitions.identity.example}",
"access": "private",
"method": "DELETE",
"rel": "delete",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"targetSchema": {
"type": "boolean"
}
},
{
"title": "Set Password",
"description": "Sets a password for an existing User",
"href": "/users/{definitions.identity.example}/auth",
"access": "private",
"method": "PUT",
"rel": "update",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"schema": {
"type": "object",
"required": [
"type",
"secret"
],
"properties": {
"type": {
"type": "string",
"pattern": "^password$"
},
"current": {
"type": "string",
"minLength": 1,
"maxLength": 64
},
"secret": {
"type": "string",
"minLength": 8,
"maxLength": 64
}
}
},
"targetSchema": {
"type": "boolean"
}
},
{
"title": "Set Permissions",
"description": "Sets Permissions for a User",
"href": "/users/{definitions.identity.example}/permissions",
"access": "private",
"method": "PUT",
"rel": "update",
"http_header": {
"$ref": "../examples.json#/definitions/auth_header"
},
"schema": {
"type": "object",
"properties": {
"visibility": {
"type": "string",
"pattern": "^(all|user)$"
},
"access_lists": {
"type": "string",
"pattern": "^(hidden|view|manage)$"
},
"dead_hosts": {
"type": "string",
"pattern": "^(hidden|view|manage)$"
},
"proxy_hosts": {
"type": "string",
"pattern": "^(hidden|view|manage)$"
},
"redirection_hosts": {
"type": "string",
"pattern": "^(hidden|view|manage)$"
},
"streams": {
"type": "string",
"pattern": "^(hidden|view|manage)$"
},
"certificates": {
"type": "string",
"pattern": "^(hidden|view|manage)$"
}
}
},
"targetSchema": {
"type": "boolean"
}
}
],
"properties": {
"id": {
"$ref": "#/definitions/id"
},
"created_on": {
"$ref": "#/definitions/created_on"
},
"modified_on": {
"$ref": "#/definitions/modified_on"
},
"name": {
"$ref": "#/definitions/name"
},
"nickname": {
"$ref": "#/definitions/nickname"
},
"email": {
"$ref": "#/definitions/email"
},
"avatar": {
"$ref": "#/definitions/avatar"
},
"roles": {
"$ref": "#/definitions/roles"
},
"is_disabled": {
"$ref": "#/definitions/is_disabled"
}
}
}

View File

@ -0,0 +1,23 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "examples",
"type": "object",
"definitions": {
"name": {
"description": "Name",
"example": "John Smith",
"type": "string",
"minLength": 1,
"maxLength": 255
},
"auth_header": {
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.O_frfYM8RzmRsUNigHtu0_jZ_utSejyr1axMGa8rlsk",
"X-API-Version": "next"
},
"token": {
"type": "string",
"description": "JWT",
"example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.O_frfYM8RzmRsUNigHtu0_jZ_utSejyr1axMGa8rlsk"
}
}
}

View File

@ -1,41 +0,0 @@
const refParser = require('@apidevtools/json-schema-ref-parser');
let compiledSchema = null;
module.exports = {
/**
* Compiles the schema, by dereferencing it, only once
* and returns the memory cached value
*/
getCompiledSchema: async () => {
if (compiledSchema === null) {
compiledSchema = await refParser.dereference(__dirname + '/swagger.json', {
mutateInputSchema: false,
});
}
return compiledSchema;
},
/**
* Scans the schema for the validation schema for the given path and method
* and returns it.
*
* @param {string} path
* @param {string} method
* @returns string|null
*/
getValidationSchema: (path, method) => {
if (compiledSchema !== null &&
typeof compiledSchema.paths[path] !== 'undefined' &&
typeof compiledSchema.paths[path][method] !== 'undefined' &&
typeof compiledSchema.paths[path][method].requestBody !== 'undefined' &&
typeof compiledSchema.paths[path][method].requestBody.content !== 'undefined' &&
typeof compiledSchema.paths[path][method].requestBody.content['application/json'] !== 'undefined' &&
typeof compiledSchema.paths[path][method].requestBody.content['application/json'].schema !== 'undefined'
) {
return compiledSchema.paths[path][method].requestBody.content['application/json'].schema;
}
return null;
}
};

42
backend/schema/index.json Normal file
View File

@ -0,0 +1,42 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "root",
"title": "Nginx Proxy Manager REST API",
"description": "This is the Nginx Proxy Manager REST API",
"version": "2.0.0",
"links": [
{
"href": "http://npm.example.com/api",
"rel": "self"
}
],
"properties": {
"tokens": {
"$ref": "endpoints/tokens.json"
},
"users": {
"$ref": "endpoints/users.json"
},
"proxy-hosts": {
"$ref": "endpoints/proxy-hosts.json"
},
"redirection-hosts": {
"$ref": "endpoints/redirection-hosts.json"
},
"dead-hosts": {
"$ref": "endpoints/dead-hosts.json"
},
"streams": {
"$ref": "endpoints/streams.json"
},
"certificates": {
"$ref": "endpoints/certificates.json"
},
"access-lists": {
"$ref": "endpoints/access-lists.json"
},
"settings": {
"$ref": "endpoints/settings.json"
}
}
}

View File

@ -1,53 +0,0 @@
{
"operationId": "getAuditLog",
"summary": "Get Audit Log",
"tags": ["Audit Log"],
"security": [
{
"BearerAuth": ["audit-log"]
}
],
"responses": {
"200": {
"description": "200 response",
"content": {
"application/json": {
"examples": {
"default": {
"value": [
{
"id": 7,
"created_on": "2024-10-08T13:09:54.000Z",
"modified_on": "2024-10-08T13:09:54.000Z",
"user_id": 1,
"object_type": "user",
"object_id": 3,
"action": "updated",
"meta": {
"name": "John Doe",
"permissions": {
"user_id": 3,
"visibility": "all",
"access_lists": "manage",
"dead_hosts": "hidden",
"proxy_hosts": "manage",
"redirection_hosts": "view",
"streams": "hidden",
"certificates": "manage",
"id": 3,
"modified_on": "2024-10-08T13:09:54.000Z",
"created_on": "2024-10-08T13:09:51.000Z"
}
}
}
]
}
},
"schema": {
"$ref": "../../components/audit-log-object.json"
}
}
}
}
}
}

View File

@ -1,29 +0,0 @@
{
"operationId": "health",
"summary": "Returns the API health status",
"tags": ["Public"],
"responses": {
"200": {
"description": "200 response",
"content": {
"application/json": {
"examples": {
"default": {
"value": {
"status": "OK",
"version": {
"major": 2,
"minor": 1,
"revision": 0
}
}
}
},
"schema": {
"$ref": "../components/health-object.json"
}
}
}
}
}
}

View File

@ -1,50 +0,0 @@
{
"operationId": "getAccessLists",
"summary": "Get all access lists",
"tags": ["Access Lists"],
"security": [
{
"BearerAuth": ["access_lists"]
}
],
"parameters": [
{
"in": "query",
"name": "expand",
"description": "Expansions",
"schema": {
"type": "string",
"enum": ["owner", "items", "clients", "proxy_hosts"]
}
}
],
"responses": {
"200": {
"description": "200 response",
"content": {
"application/json": {
"examples": {
"default": {
"value": [
{
"id": 1,
"created_on": "2024-10-08T22:15:40.000Z",
"modified_on": "2024-10-08T22:15:40.000Z",
"owner_user_id": 1,
"name": "test1234",
"meta": {},
"satisfy_any": true,
"pass_auth": false,
"proxy_host_count": 0
}
]
}
},
"schema": {
"$ref": "../../../components/access-list-object.json"
}
}
}
}
}
}

View File

@ -1,39 +0,0 @@
{
"operationId": "deleteAccessList",
"summary": "Delete a Access List",
"tags": ["Access Lists"],
"security": [
{
"BearerAuth": ["access_lists"]
}
],
"parameters": [
{
"in": "path",
"name": "listID",
"schema": {
"type": "integer",
"minimum": 1
},
"required": true,
"example": 2
}
],
"responses": {
"200": {
"description": "200 response",
"content": {
"application/json": {
"examples": {
"default": {
"value": true
}
},
"schema": {
"type": "boolean"
}
}
}
}
}
}

View File

@ -1,49 +0,0 @@
{
"operationId": "getAccessList",
"summary": "Get a access List",
"tags": ["Access Lists"],
"security": [
{
"BearerAuth": ["access_lists"]
}
],
"parameters": [
{
"in": "path",
"name": "listID",
"schema": {
"type": "integer",
"minimum": 1
},
"required": true,
"example": 1
}
],
"responses": {
"200": {
"description": "200 response",
"content": {
"application/json": {
"examples": {
"default": {
"value": {
"id": 1,
"created_on": "2020-01-30T09:36:08.000Z",
"modified_on": "2020-01-30T09:41:04.000Z",
"is_disabled": false,
"email": "jc@jc21.com",
"name": "Jamie Curnow",
"nickname": "James",
"avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
"roles": ["admin"]
}
}
},
"schema": {
"$ref": "../../../../components/access-list-object.json"
}
}
}
}
}
}

View File

@ -1,163 +0,0 @@
{
"operationId": "updateAccessList",
"summary": "Update a Access List",
"tags": ["Access Lists"],
"security": [
{
"BearerAuth": ["access_lists"]
}
],
"parameters": [
{
"in": "path",
"name": "listID",
"schema": {
"type": "integer",
"minimum": 1
},
"required": true,
"example": 2
}
],
"requestBody": {
"description": "Access List Payload",
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": false,
"minProperties": 1,
"properties": {
"name": {
"$ref": "../../../../components/access-list-object.json#/properties/name"
},
"satisfy_any": {
"$ref": "../../../../components/access-list-object.json#/properties/satisfy_any"
},
"pass_auth": {
"$ref": "../../../../components/access-list-object.json#/properties/pass_auth"
},
"items": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"username": {
"type": "string",
"minLength": 1
},
"password": {
"type": "string"
}
}
}
},
"clients": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"address": {
"oneOf": [
{
"type": "string",
"pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$"
},
{
"type": "string",
"pattern": "^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$"
},
{
"type": "string",
"pattern": "^all$"
}
]
},
"directive": {
"$ref": "../../../../components/access-list-object.json#/properties/directive"
}
}
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "200 response",
"content": {
"application/json": {
"examples": {
"default": {
"value": {
"id": 1,
"created_on": "2024-10-08T22:15:40.000Z",
"modified_on": "2024-10-08T22:34:34.000Z",
"owner_user_id": 1,
"name": "test123!!",
"meta": {},
"satisfy_any": true,
"pass_auth": false,
"proxy_host_count": 0,
"owner": {
"id": 1,
"created_on": "2024-10-07T22:43:55.000Z",
"modified_on": "2024-10-08T12:52:54.000Z",
"is_deleted": false,
"is_disabled": false,
"email": "admin@example.com",
"name": "Administrator",
"nickname": "some guy",
"avatar": "//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?default=mm",
"roles": ["admin"]
},
"items": [
{
"id": 1,
"created_on": "2024-10-08T22:15:40.000Z",
"modified_on": "2024-10-08T22:15:40.000Z",
"access_list_id": 1,
"username": "admin",
"password": "",
"meta": {},
"hint": "a****"
},
{
"id": 2,
"created_on": "2024-10-08T22:15:40.000Z",
"modified_on": "2024-10-08T22:15:40.000Z",
"access_list_id": 1,
"username": "asdad",
"password": "",
"meta": {},
"hint": "a*****"
}
],
"clients": [
{
"id": 1,
"created_on": "2024-10-08T22:15:40.000Z",
"modified_on": "2024-10-08T22:15:40.000Z",
"access_list_id": 1,
"address": "127.0.0.1",
"directive": "allow",
"meta": {}
}
],
"proxy_hosts": []
}
}
},
"schema": {
"$ref": "../../../../components/access-list-object.json"
}
}
}
}
}
}

View File

@ -1,155 +0,0 @@
{
"operationId": "createAccessList",
"summary": "Create a Access List",
"tags": ["Access Lists"],
"security": [
{
"BearerAuth": ["access_lists"]
}
],
"requestBody": {
"description": "Access List Payload",
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": false,
"required": ["name"],
"properties": {
"name": {
"$ref": "../../../components/access-list-object.json#/properties/name"
},
"satisfy_any": {
"$ref": "../../../components/access-list-object.json#/properties/satisfy_any"
},
"pass_auth": {
"$ref": "../../../components/access-list-object.json#/properties/pass_auth"
},
"items": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"username": {
"type": "string",
"minLength": 1
},
"password": {
"type": "string",
"minLength": 1
}
}
}
},
"clients": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"address": {
"oneOf": [
{
"type": "string",
"pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$"
},
{
"type": "string",
"pattern": "^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$"
},
{
"type": "string",
"pattern": "^all$"
}
]
},
"directive": {
"$ref": "../../../components/access-list-object.json#/properties/directive"
}
}
}
},
"meta": {
"$ref": "../../../components/access-list-object.json#/properties/meta"
}
}
}
}
}
},
"responses": {
"201": {
"description": "201 response",
"content": {
"application/json": {
"examples": {
"default": {
"value": {
"id": 1,
"created_on": "2024-10-08T22:15:40.000Z",
"modified_on": "2024-10-08T22:15:40.000Z",
"owner_user_id": 1,
"name": "test1234",
"meta": {},
"satisfy_any": true,
"pass_auth": false,
"proxy_host_count": 0,
"owner": {
"id": 1,
"created_on": "2024-10-07T22:43:55.000Z",
"modified_on": "2024-10-08T12:52:54.000Z",
"is_deleted": false,
"is_disabled": false,
"email": "admin@example.com",
"name": "Administrator",
"nickname": "some guy",
"avatar": "//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?default=mm",
"roles": ["admin"]
},
"items": [
{
"id": 1,
"created_on": "2024-10-08T22:15:40.000Z",
"modified_on": "2024-10-08T22:15:40.000Z",
"access_list_id": 1,
"username": "admin",
"password": "",
"meta": {},
"hint": "a****"
},
{
"id": 2,
"created_on": "2024-10-08T22:15:40.000Z",
"modified_on": "2024-10-08T22:15:40.000Z",
"access_list_id": 1,
"username": "asdad",
"password": "",
"meta": {},
"hint": "a*****"
}
],
"proxy_hosts": [],
"clients": [
{
"id": 1,
"created_on": "2024-10-08T22:15:40.000Z",
"modified_on": "2024-10-08T22:15:40.000Z",
"access_list_id": 1,
"address": "127.0.0.1",
"directive": "allow",
"meta": {}
}
]
}
}
},
"schema": {
"$ref": "../../../components/access-list-object.json"
}
}
}
}
}
}

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