Compare commits

..

13 Commits

Author SHA1 Message Date
jc21
fdb22e467b Merge pull request #3952 from Trozz/openidc
Merge develop in to openidc
2024-10-11 14:12:20 +10:00
Michael Leer
694d8a0f21 Merge branch 'develop' into openidc 2024-08-22 23:19:50 +01:00
Jamie Curnow
a91dcb144d Use model for db defaults as sqlite doesn't support them 2021-09-09 08:12:50 +10:00
Subv
e7f7be2a2b OpenIDC: Trigger the change event of the "restrict users" toggle when enabling/disabling oidc.
If this is not triggered and the OIDC toggle is enabled, the "disabled" property will be removed from the restricted user list input, causing an error when trying to submit the form without it.
2021-09-09 08:12:50 +10:00
Subv
076d89b5b5 Use localized strings for the OpenID Connect texts. 2021-09-09 08:12:50 +10:00
Subv
8539930f89 Updated the docs to add a section about OpenID Connect 2021-09-09 08:12:50 +10:00
Subv
87d9babbd3 Fix conditionals in the liquid template for OpenID Connect conf. 2021-09-09 08:12:50 +10:00
Subv
9f2d3a1737 Manually set the default values for the OpenID Connect columns.
There is a Knex issue ( https://github.com/knex/knex/issues/2649 ) that prevents .defaultTo from working for text columns.
2021-09-09 08:12:50 +10:00
Subv
daf399163c Allow limiting OpenID Connect auth to a list of users. 2021-09-09 08:12:50 +10:00
Subv
cdf702e545 Add a field to specify a list of allowed emails when using OpenID Connect auth. 2021-09-09 08:12:50 +10:00
Subv
5811345050 Use OpenResty instead of plain nginx to support OpenID Connect authorization. 2021-09-09 08:12:48 +10:00
Subv
53792a5cf7 Add database columns to store OpenID Connect information for Proxy Hosts. 2021-09-09 08:12:19 +10:00
Subv
8e10b7da37 Add UI tab for specifying OpenID Connect options for proxy hosts. 2021-09-09 08:12:19 +10:00
241 changed files with 7962 additions and 11215 deletions

View File

@@ -1 +1 @@
2.12.6 2.11.3

82
Jenkinsfile vendored
View File

@@ -43,7 +43,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,13 +56,6 @@ 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('Builds') { stage('Builds') {
@@ -127,11 +120,6 @@ pipeline {
junit 'test/results/junit/*' junit 'test/results/junit/*'
sh 'docker-compose down --remove-orphans --volumes -t 30 || true' sh 'docker-compose down --remove-orphans --volumes -t 30 || true'
} }
unstable {
dir(path: 'test/results') {
archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml')
}
}
} }
} }
stage('Test Mysql') { stage('Test Mysql') {
@@ -160,49 +148,6 @@ pipeline {
junit 'test/results/junit/*' junit 'test/results/junit/*'
sh 'docker-compose down --remove-orphans --volumes -t 30 || true' 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/*'
sh 'docker-compose down --remove-orphans --volumes -t 30 || true'
}
unstable {
dir(path: 'test/results') {
archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml')
}
}
} }
} }
stage('MultiArch Build') { stage('MultiArch Build') {
@@ -212,9 +157,12 @@ 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') { stage('Docs / Comment') {
parallel { parallel {
stage('Docs Job') { stage('Docs Job') {
@@ -241,18 +189,7 @@ pipeline {
} }
steps { steps {
script { script {
npmGithubPrComment("""Docker Image for build ${BUILD_NUMBER} is available on [DockerHub](https://cloud.docker.com/repository/docker/nginxproxymanager/${IMAGE}-dev): 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)
```
nginxproxymanager/${IMAGE}-dev:${BRANCH_LOWER}
```
> [!NOTE]
> Ensure you backup your NPM instance before testing this image! Especially if there are database changes.
> This is a different docker image namespace than the official image.
> [!WARNING]
> Changes and additions to DNS Providers require verification by at least 2 members of the community!
""", true)
} }
} }
} }
@@ -263,13 +200,20 @@ nginxproxymanager/${IMAGE}-dev:${BRANCH_LOWER}
always { always {
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.6-green.svg?style=for-the-badge"> <img src="https://img.shields.io/badge/version-2.11.3-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>

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",

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,31 +1,23 @@
#!/usr/bin/env node #!/usr/bin/env node
const schema = require('./schema');
const logger = require('./logger').global; const logger = require('./logger').global;
const IP_RANGES_FETCH_ENABLED = process.env.IP_RANGES_FETCH_ENABLED !== 'false';
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(() => { .then(() => {
if (IP_RANGES_FETCH_ENABLED) { return apiValidator.loadSchemas;
logger.info('IP Ranges fetch is enabled');
return internalIpRanges.fetch().catch((err) => {
logger.error('IP Ranges fetch failed, continuing anyway:', err.message);
});
} else {
logger.info('IP Ranges fetch is disabled by environment variable');
}
}) })
.then(internalIpRanges.fetch)
.then(() => { .then(() => {
internalCertificate.initTimer(); internalCertificate.initTimer();
internalIpRanges.initTimer(); internalIpRanges.initTimer();
@@ -42,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

@@ -1,5 +1,5 @@
const _ = require('lodash'); const _ = require('lodash');
const fs = require('node:fs'); const fs = require('fs');
const batchflow = require('batchflow'); const batchflow = require('batchflow');
const logger = require('../logger').access; const logger = require('../logger').access;
const error = require('../lib/error'); const error = require('../lib/error');
@@ -38,7 +38,7 @@ const internalAccessList = {
.then((row) => { .then((row) => {
data.id = row.id; data.id = row.id;
const promises = []; let promises = [];
// Now add the items // Now add the items
data.items.map((item) => { data.items.map((item) => {
@@ -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);
} }
}) })
@@ -116,7 +116,7 @@ const internalAccessList = {
.then((row) => { .then((row) => {
if (row.id !== data.id) { if (row.id !== data.id) {
// Sanity check that something crazy hasn't happened // Sanity check that something crazy hasn't happened
throw new error.InternalValidationError(`Access List could not be updated, IDs do not match: ${row.id} !== ${data.id}`); throw new error.InternalValidationError('Access List could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
} }
}) })
.then(() => { .then(() => {
@@ -135,10 +135,10 @@ const internalAccessList = {
.then(() => { .then(() => {
// Check for items and add/update/remove them // Check for items and add/update/remove them
if (typeof data.items !== 'undefined' && data.items) { if (typeof data.items !== 'undefined' && data.items) {
const promises = []; let promises = [];
const items_to_keep = []; let items_to_keep = [];
data.items.map((item) => { data.items.map(function (item) {
if (item.password) { if (item.password) {
promises.push(accessListAuthModel promises.push(accessListAuthModel
.query() .query()
@@ -154,7 +154,7 @@ const internalAccessList = {
} }
}); });
const query = accessListAuthModel let query = accessListAuthModel
.query() .query()
.delete() .delete()
.where('access_list_id', data.id); .where('access_list_id', data.id);
@@ -175,9 +175,9 @@ const internalAccessList = {
.then(() => { .then(() => {
// Check for clients and add/update/remove them // Check for clients and add/update/remove them
if (typeof data.clients !== 'undefined' && data.clients) { if (typeof data.clients !== 'undefined' && data.clients) {
const promises = []; let promises = [];
data.clients.map((client) => { data.clients.map(function (client) {
if (client.address) { if (client.address) {
promises.push(accessListClientModel promises.push(accessListClientModel
.query() .query()
@@ -190,7 +190,7 @@ const internalAccessList = {
} }
}); });
const query = accessListClientModel let query = accessListClientModel
.query() .query()
.delete() .delete()
.where('access_list_id', data.id); .where('access_list_id', data.id);
@@ -223,7 +223,7 @@ 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(internalNginx.reload)
@@ -249,16 +249,12 @@ const internalAccessList = {
return access.can('access_lists:get', data.id) return access.can('access_lists:get', data.id)
.then((access_data) => { .then((access_data) => {
const query = accessListModel let query = accessListModel
.query() .query()
.select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count')) .select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count'))
.leftJoin('proxy_host', function() { .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();
@@ -267,13 +263,13 @@ const internalAccessList = {
} }
if (typeof data.expand !== 'undefined' && data.expand !== null) { if (typeof data.expand !== 'undefined' && data.expand !== null) {
query.withGraphFetched(`[${data.expand.join(', ')}]`); query.withGraphFetched('[' + data.expand.join(', ') + ']');
} }
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 +296,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);
} }
@@ -327,7 +323,7 @@ const internalAccessList = {
// 3. reconfigure those hosts, then reload nginx // 3. reconfigure those hosts, then reload nginx
// set the access_list_id to zero for these items // set the access_list_id to zero for these items
row.proxy_hosts.map((_val, idx) => { row.proxy_hosts.map(function (val, idx) {
row.proxy_hosts[idx].access_list_id = 0; row.proxy_hosts[idx].access_list_id = 0;
}); });
@@ -340,11 +336,11 @@ const internalAccessList = {
}) })
.then(() => { .then(() => {
// delete the htpasswd file // delete the htpasswd file
const htpasswd_file = internalAccessList.getFilename(row); let htpasswd_file = internalAccessList.getFilename(row);
try { try {
fs.unlinkSync(htpasswd_file); fs.unlinkSync(htpasswd_file);
} catch (_err) { } catch (err) {
// do nothing // do nothing
} }
}) })
@@ -374,13 +370,10 @@ const internalAccessList = {
getAll: (access, expand, search_query) => { getAll: (access, expand, search_query) => {
return access.can('access_lists:list') return access.can('access_lists:list')
.then((access_data) => { .then((access_data) => {
const query = accessListModel let query = accessListModel
.query() .query()
.select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count')) .select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count'))
.leftJoin('proxy_host', function() { .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]')
@@ -393,19 +386,19 @@ const internalAccessList = {
// Query is used for searching // Query is used for searching
if (typeof search_query === 'string') { if (typeof search_query === 'string') {
query.where(function () { query.where(function () {
this.where('name', 'like', `%${search_query}%`); this.where('name', 'like', '%' + search_query + '%');
}); });
} }
if (typeof expand !== 'undefined' && expand !== null) { if (typeof expand !== 'undefined' && expand !== null) {
query.withGraphFetched(`[${expand.join(', ')}]`); query.withGraphFetched('[' + expand.join(', ') + ']');
} }
return query.then(utils.omitRows(omissions())); return query.then(utils.omitRows(omissions()));
}) })
.then((rows) => { .then((rows) => {
if (rows) { if (rows) {
rows.map((row, idx) => { rows.map(function (row, idx) {
if (typeof row.items !== 'undefined' && row.items) { if (typeof row.items !== 'undefined' && row.items) {
rows[idx] = internalAccessList.maskItems(row); rows[idx] = internalAccessList.maskItems(row);
} }
@@ -424,7 +417,7 @@ const internalAccessList = {
* @returns {Promise} * @returns {Promise}
*/ */
getCount: (user_id, visibility) => { getCount: (user_id, visibility) => {
const query = accessListModel let query = accessListModel
.query() .query()
.count('id as count') .count('id as count')
.where('is_deleted', 0); .where('is_deleted', 0);
@@ -445,7 +438,7 @@ const internalAccessList = {
*/ */
maskItems: (list) => { maskItems: (list) => {
if (list && typeof list.items !== 'undefined') { if (list && typeof list.items !== 'undefined') {
list.items.map((val, idx) => { list.items.map(function (val, idx) {
let repeat_for = 8; let repeat_for = 8;
let first_char = '*'; let first_char = '*';
@@ -468,7 +461,7 @@ const internalAccessList = {
* @returns {String} * @returns {String}
*/ */
getFilename: (list) => { getFilename: (list) => {
return `/data/access/${list.id}`; return '/data/access/' + list.id;
}, },
/** /**
@@ -479,15 +472,15 @@ const internalAccessList = {
* @returns {Promise} * @returns {Promise}
*/ */
build: (list) => { build: (list) => {
logger.info(`Building Access file #${list.id} for: ${list.name}`); logger.info('Building Access file #' + list.id + ' for: ' + list.name);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const htpasswd_file = internalAccessList.getFilename(list); let htpasswd_file = internalAccessList.getFilename(list);
// 1. remove any existing access file // 1. remove any existing access file
try { try {
fs.unlinkSync(htpasswd_file); fs.unlinkSync(htpasswd_file);
} catch (_err) { } catch (err) {
// do nothing // do nothing
} }
@@ -504,17 +497,12 @@ const internalAccessList = {
if (list.items.length) { if (list.items.length) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
batchflow(list.items).sequential() batchflow(list.items).sequential()
.each((_i, item, next) => { .each((i, item, next) => {
if (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) => {
@@ -528,7 +516,7 @@ const internalAccessList = {
reject(err); reject(err);
}) })
.end((results) => { .end((results) => {
logger.success(`Built Access file #${list.id} for: ${list.name}`); logger.success('Built Access file #' + list.id + ' for: ' + list.name);
resolve(results); resolve(results);
}); });
}); });

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

@@ -1,31 +1,29 @@
const _ = require('lodash'); const _ = require('lodash');
const fs = require('node:fs'); const fs = require('fs');
const https = require('node: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 tokenModel = require('../models/token');
const dnsPlugins = require('../global/certbot-dns-plugins.json'); 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 certbot = require('../lib/certbot');
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 = {
@@ -49,7 +47,7 @@ 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 expiring within ' + internalCertificate.renewBeforeExpirationBy[0] + ' ' + internalCertificate.renewBeforeExpirationBy[1] + ' ...');
const expirationThreshold = moment().add(internalCertificate.renewBeforeExpirationBy[0], internalCertificate.renewBeforeExpirationBy[1]).format('YYYY-MM-DD HH:mm:ss'); const expirationThreshold = moment().add(internalCertificate.renewBeforeExpirationBy[0], internalCertificate.renewBeforeExpirationBy[1]).format('YYYY-MM-DD HH:mm:ss');
@@ -70,7 +68,7 @@ const internalCertificate = {
*/ */
let sequence = Promise.resolve(); let sequence = Promise.resolve();
certificates.forEach((certificate) => { certificates.forEach(function (certificate) {
sequence = sequence.then(() => sequence = sequence.then(() =>
internalCertificate internalCertificate
.renew( .renew(
@@ -202,14 +200,13 @@ const internalCertificate = {
.then(() => { .then(() => {
// At this point, the letsencrypt cert should exist on disk. // At this point, the letsencrypt cert should exist on disk.
// Lets get the expiry date from the file and update the row silently // Lets get the expiry date from the file and update the row silently
return internalCertificate.getCertificateInfoFromFile(`${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`) return internalCertificate.getCertificateInfoFromFile('/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem')
.then((cert_info) => { .then((cert_info) => {
return certificateModel return certificateModel
.query() .query()
.patchAndFetchById(certificate.id, { .patchAndFetchById(certificate.id, {
expires_on: moment(cert_info.dates.to, 'X').format('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, {
@@ -263,7 +260,7 @@ const internalCertificate = {
.then((row) => { .then((row) => {
if (row.id !== data.id) { if (row.id !== data.id) {
// Sanity check that something crazy hasn't happened // Sanity check that something crazy hasn't happened
throw new error.InternalValidationError(`Certificate could not be updated, IDs do not match: ${row.id} !== ${data.id}`); throw new error.InternalValidationError('Certificate could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
} }
return certificateModel return certificateModel
@@ -308,14 +305,11 @@ const internalCertificate = {
return access.can('certificates:get', data.id) return access.can('certificates:get', data.id)
.then((access_data) => { .then((access_data) => {
const query = certificateModel let query = certificateModel
.query() .query()
.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') {
@@ -323,13 +317,13 @@ const internalCertificate = {
} }
if (typeof data.expand !== 'undefined' && data.expand !== null) { if (typeof data.expand !== 'undefined' && data.expand !== null) {
query.withGraphFetched(`[${data.expand.join(', ')}]`); query.withGraphFetched('[' + data.expand.join(', ') + ']');
} }
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
@@ -354,17 +348,17 @@ const internalCertificate = {
}) })
.then((certificate) => { .then((certificate) => {
if (certificate.provider === 'letsencrypt') { if (certificate.provider === 'letsencrypt') {
const zipDirectory = internalCertificate.getLiveCertPath(data.id); const zipDirectory = '/etc/letsencrypt/live/npm-' + data.id;
if (!fs.existsSync(zipDirectory)) { if (!fs.existsSync(zipDirectory)) {
throw new error.ItemNotFoundError(`Certificate ${certificate.nice_name} does not exists`); throw new error.ItemNotFoundError('Certificate ' + certificate.nice_name + ' does not exists');
} }
const certFiles = fs.readdirSync(zipDirectory) let certFiles = fs.readdirSync(zipDirectory)
.filter((fn) => fn.endsWith('.pem')) .filter((fn) => fn.endsWith('.pem'))
.map((fn) => fs.realpathSync(path.join(zipDirectory, fn))); .map((fn) => fs.realpathSync(path.join(zipDirectory, fn)));
const downloadName = `npm-${data.id}-${Date.now()}.zip`; const downloadName = 'npm-' + data.id + '-' + `${Date.now()}.zip`;
const opName = `/tmp/${downloadName}`; const opName = '/tmp/' + downloadName;
internalCertificate.zipFiles(certFiles, opName) internalCertificate.zipFiles(certFiles, opName)
.then(() => { .then(() => {
logger.debug('zip completed : ', opName); logger.debug('zip completed : ', opName);
@@ -392,7 +386,7 @@ const internalCertificate = {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
source source
.map((fl) => { .map((fl) => {
const fileName = path.basename(fl); let fileName = path.basename(fl);
logger.debug(fl, 'added to certificate zip'); logger.debug(fl, 'added to certificate zip');
archive.file(fl, { name: fileName }); archive.file(fl, { name: fileName });
}); });
@@ -418,7 +412,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);
} }
@@ -462,14 +456,11 @@ const internalCertificate = {
getAll: (access, expand, search_query) => { getAll: (access, expand, search_query) => {
return access.can('certificates:list') return access.can('certificates:list')
.then((access_data) => { .then((access_data) => {
const query = certificateModel let query = certificateModel
.query() .query()
.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') {
@@ -479,12 +470,12 @@ const internalCertificate = {
// Query is used for searching // Query is used for searching
if (typeof search_query === 'string') { if (typeof search_query === 'string') {
query.where(function () { query.where(function () {
this.where('nice_name', 'like', `%${search_query}%`); this.where('nice_name', 'like', '%' + search_query + '%');
}); });
} }
if (typeof expand !== 'undefined' && expand !== null) { if (typeof expand !== 'undefined' && expand !== null) {
query.withGraphFetched(`[${expand.join(', ')}]`); query.withGraphFetched('[' + expand.join(', ') + ']');
} }
return query.then(utils.omitRows(omissions())); return query.then(utils.omitRows(omissions()));
@@ -499,7 +490,7 @@ const internalCertificate = {
* @returns {Promise} * @returns {Promise}
*/ */
getCount: (user_id, visibility) => { getCount: (user_id, visibility) => {
const query = certificateModel let query = certificateModel
.query() .query()
.count('id as count') .count('id as count')
.where('is_deleted', 0); .where('is_deleted', 0);
@@ -521,7 +512,7 @@ const internalCertificate = {
writeCustomCert: (certificate) => { writeCustomCert: (certificate) => {
logger.info('Writing Custom Certificate:', certificate); logger.info('Writing Custom Certificate:', certificate);
const dir = `/data/custom_ssl/npm-${certificate.id}`; const dir = '/data/custom_ssl/npm-' + certificate.id;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (certificate.provider === 'letsencrypt') { if (certificate.provider === 'letsencrypt') {
@@ -531,7 +522,7 @@ const internalCertificate = {
let certData = certificate.meta.certificate; let certData = certificate.meta.certificate;
if (typeof certificate.meta.intermediate_certificate !== 'undefined') { if (typeof certificate.meta.intermediate_certificate !== 'undefined') {
certData = `${certData}\n${certificate.meta.intermediate_certificate}`; certData = certData + '\n' + certificate.meta.intermediate_certificate;
} }
try { try {
@@ -543,7 +534,7 @@ const internalCertificate = {
return; return;
} }
fs.writeFile(`${dir}/fullchain.pem`, certData, (err) => { fs.writeFile(dir + '/fullchain.pem', certData, function (err) {
if (err) { if (err) {
reject(err); reject(err);
} else { } else {
@@ -553,7 +544,7 @@ const internalCertificate = {
}) })
.then(() => { .then(() => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fs.writeFile(`${dir}/privkey.pem`, certificate.meta.certificate_key, (err) => { fs.writeFile(dir + '/privkey.pem', certificate.meta.certificate_key, function (err) {
if (err) { if (err) {
reject(err); reject(err);
} else { } else {
@@ -591,7 +582,7 @@ const internalCertificate = {
validate: (data) => { validate: (data) => {
return new Promise((resolve) => { return new Promise((resolve) => {
// Put file contents into an object // Put file contents into an object
const files = {}; let files = {};
_.map(data.files, (file, name) => { _.map(data.files, (file, name) => {
if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) { if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) {
files[name] = file.data.toString(); files[name] = file.data.toString();
@@ -603,7 +594,7 @@ const internalCertificate = {
.then((files) => { .then((files) => {
// For each file, create a temp file and write the contents to it // For each file, create a temp file and write the contents to it
// Then test it depending on the file type // Then test it depending on the file type
const promises = []; let promises = [];
_.map(files, (content, type) => { _.map(files, (content, type) => {
promises.push(new Promise((resolve) => { promises.push(new Promise((resolve) => {
if (type === 'certificate_key') { if (type === 'certificate_key') {
@@ -688,11 +679,11 @@ const internalCertificate = {
reject(new error.ValidationError('Result Validation Error: Validation timed out. This could be due to the key being passphrase-protected.')); reject(new error.ValidationError('Result Validation Error: Validation timed out. This could be due to the key being passphrase-protected.'));
}, 10000); }, 10000);
utils utils
.exec(`openssl pkey -in ${filepath} -check -noout 2>&1 `) .exec('openssl pkey -in ' + filepath + ' -check -noout 2>&1 ')
.then((result) => { .then((result) => {
clearTimeout(failTimeout); clearTimeout(failTimeout);
if (!result.toLowerCase().includes('key is valid')) { if (!result.toLowerCase().includes('key is valid')) {
reject(new error.ValidationError(`Result Validation Error: ${result}`)); reject(new error.ValidationError('Result Validation Error: ' + result));
} }
fs.unlinkSync(filepath); fs.unlinkSync(filepath);
resolve(true); resolve(true);
@@ -700,7 +691,7 @@ const internalCertificate = {
.catch((err) => { .catch((err) => {
clearTimeout(failTimeout); clearTimeout(failTimeout);
fs.unlinkSync(filepath); fs.unlinkSync(filepath);
reject(new error.ValidationError(`Certificate Key is not valid (${err.message})`, err)); reject(new error.ValidationError('Certificate Key is not valid (' + err.message + ')', err));
}); });
}); });
}); });
@@ -735,36 +726,36 @@ const internalCertificate = {
* @param {Boolean} [throw_expired] Throw when the certificate is out of date * @param {Boolean} [throw_expired] Throw when the certificate is out of date
*/ */
getCertificateInfoFromFile: (certificate_file, throw_expired) => { getCertificateInfoFromFile: (certificate_file, throw_expired) => {
const certData = {}; let certData = {};
return utils.execFile('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.execFile('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.execFile('openssl', ['x509', '-in', certificate_file, '-dates', '-noout']); return utils.exec('openssl x509 -in ' + certificate_file + ' -dates -noout');
}) })
.then((result) => { .then((result) => {
// notBefore=Jul 14 04:04:29 2018 GMT // notBefore=Jul 14 04:04:29 2018 GMT
@@ -773,7 +764,7 @@ const internalCertificate = {
let validTo = null; let validTo = null;
const lines = result.split('\n'); const lines = result.split('\n');
lines.map((str) => { lines.map(function (str) {
const regex = /^(\S+)=(.*)$/gim; const regex = /^(\S+)=(.*)$/gim;
const match = regex.exec(str.trim()); const match = regex.exec(str.trim());
@@ -789,21 +780,21 @@ const internalCertificate = {
}); });
if (!validFrom || !validTo) { if (!validFrom || !validTo) {
throw new error.ValidationError(`Could not determine dates from certificate: ${result}`); throw new error.ValidationError('Could not determine dates from certificate: ' + result);
} }
if (throw_expired && validTo < parseInt(moment().format('X'), 10)) { if (throw_expired && validTo < parseInt(moment().format('X'), 10)) {
throw new error.ValidationError('Certificate has expired'); throw new error.ValidationError('Certificate has expired');
} }
certData.dates = { certData['dates'] = {
from: validFrom, from: validFrom,
to: validTo to: validTo
}; };
return certData; return certData;
}).catch((err) => { }).catch((err) => {
throw new error.ValidationError(`Certificate is not valid (${err.message})`, err); throw new error.ValidationError('Certificate is not valid (' + err.message + ')', err);
}); });
}, },
@@ -814,7 +805,7 @@ const internalCertificate = {
* @param {Boolean} [remove] * @param {Boolean} [remove]
* @returns {Object} * @returns {Object}
*/ */
cleanMeta: (meta, remove) => { cleanMeta: function (meta, remove) {
internalCertificate.allowedSslFiles.map((key) => { internalCertificate.allowedSslFiles.map((key) => {
if (typeof meta[key] !== 'undefined' && meta[key]) { if (typeof meta[key] !== 'undefined' && meta[key]) {
if (remove) { if (remove) {
@@ -834,35 +825,23 @@ const internalCertificate = {
* @returns {Promise} * @returns {Promise}
*/ */
requestLetsEncryptSsl: (certificate) => { requestLetsEncryptSsl: (certificate) => {
logger.info(`Requesting LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); logger.info('Requesting Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
const args = [ const cmd = certbotCommand + ' certonly ' +
'certonly', '--config "' + letsencryptConfig + '" ' +
'--config', '--work-dir "/tmp/letsencrypt-lib" ' +
letsencryptConfig, '--logs-dir "/tmp/letsencrypt-log" ' +
'--work-dir', '--cert-name "npm-' + certificate.id + '" ' +
'/tmp/letsencrypt-lib', '--agree-tos ' +
'--logs-dir', '--authenticator webroot ' +
'/tmp/letsencrypt-log', '--email "' + certificate.meta.letsencrypt_email + '" ' +
'--cert-name', '--preferred-challenges "dns,http" ' +
`npm-${certificate.id}`, '--domains "' + certificate.domain_names.join(',') + '" ' +
'--agree-tos', (letsencryptStaging ? '--staging' : '');
'--authenticator',
'webroot',
'--email',
certificate.meta.letsencrypt_email,
'--preferred-challenges',
'dns,http',
'--domains',
certificate.domain_names.join(','),
];
const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id); logger.info('Command:', cmd);
args.push(...adds.args);
logger.info(`Command: ${certbotCommand} ${args ? args.join(' ') : ''}`); return utils.exec(cmd)
return utils.execFile(certbotCommand, args, adds.opts)
.then((result) => { .then((result) => {
logger.success(result); logger.success(result);
return result; return result;
@@ -879,48 +858,49 @@ const internalCertificate = {
requestLetsEncryptSslWithDnsChallenge: async (certificate) => { requestLetsEncryptSslWithDnsChallenge: async (certificate) => {
await certbot.installPlugin(certificate.meta.dns_provider); await certbot.installPlugin(certificate.meta.dns_provider);
const dnsPlugin = dnsPlugins[certificate.meta.dns_provider]; const dnsPlugin = dnsPlugins[certificate.meta.dns_provider];
logger.info(`Requesting LetsEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); logger.info(`Requesting Let'sEncrypt certificates via ${dnsPlugin.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 }); fs.mkdirSync('/etc/letsencrypt/credentials', { recursive: true });
fs.writeFileSync(credentialsLocation, certificate.meta.dns_provider_credentials, {mode: 0o600}); fs.writeFileSync(credentialsLocation, certificate.meta.dns_provider_credentials, {mode: 0o600});
// 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';
const args = [ let mainCmd = certbotCommand + ' certonly ' +
'certonly', '--config "' + letsencryptConfig + '" ' +
'--config', '--work-dir "/tmp/letsencrypt-lib" ' +
letsencryptConfig, '--logs-dir "/tmp/letsencrypt-log" ' +
'--work-dir', '--cert-name "npm-' + certificate.id + '" ' +
'/tmp/letsencrypt-lib', '--agree-tos ' +
'--logs-dir', '--email "' + certificate.meta.letsencrypt_email + '" ' +
'/tmp/letsencrypt-log', '--domains "' + certificate.domain_names.join(',') + '" ' +
'--cert-name', '--authenticator ' + dnsPlugin.full_plugin_name + ' ' +
`npm-${certificate.id}`, (
'--agree-tos', hasConfigArg
'--email', ? '--' + dnsPlugin.full_plugin_name + '-credentials "' + credentialsLocation + '"'
certificate.meta.letsencrypt_email, : ''
'--domains', ) +
certificate.domain_names.join(','), (
'--authenticator', certificate.meta.propagation_seconds !== undefined
dnsPlugin.full_plugin_name, ? ' --' + dnsPlugin.full_plugin_name + '-propagation-seconds ' + certificate.meta.propagation_seconds
]; : ''
) +
(letsencryptStaging ? ' --staging' : '');
if (hasConfigArg) { // Prepend the path to the credentials file as an environment variable
args.push(`--${dnsPlugin.full_plugin_name}-credentials`, credentialsLocation); if (certificate.meta.dns_provider === 'route53') {
} mainCmd = 'AWS_CONFIG_FILE=\'' + credentialsLocation + '\' ' + mainCmd;
if (certificate.meta.propagation_seconds !== undefined) {
args.push(`--${dnsPlugin.full_plugin_name}-propagation-seconds`, certificate.meta.propagation_seconds.toString());
} }
const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider); if (certificate.meta.dns_provider === 'duckdns') {
args.push(...adds.args); mainCmd = mainCmd + ' --dns-duckdns-no-txt-restore';
}
logger.info(`Command: ${certbotCommand} ${args ? args.join(' ') : ''}`); logger.info('Command:', mainCmd);
try { try {
const result = await utils.execFile(certbotCommand, args, adds.opts); const result = await utils.exec(mainCmd);
logger.info(result); logger.info(result);
return result; return result;
} catch (err) { } catch (err) {
@@ -948,7 +928,7 @@ const internalCertificate = {
return renewMethod(certificate) return renewMethod(certificate)
.then(() => { .then(() => {
return internalCertificate.getCertificateInfoFromFile(`${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`); return internalCertificate.getCertificateInfoFromFile('/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem');
}) })
.then((cert_info) => { .then((cert_info) => {
return certificateModel return certificateModel
@@ -980,31 +960,21 @@ const internalCertificate = {
* @returns {Promise} * @returns {Promise}
*/ */
renewLetsEncryptSsl: (certificate) => { renewLetsEncryptSsl: (certificate) => {
logger.info(`Renewing LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); logger.info('Renewing Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
const args = [ const cmd = certbotCommand + ' renew --force-renewal ' +
'renew', '--config "' + letsencryptConfig + '" ' +
'--force-renewal', '--work-dir "/tmp/letsencrypt-lib" ' +
'--config', '--logs-dir "/tmp/letsencrypt-log" ' +
letsencryptConfig, '--cert-name "npm-' + certificate.id + '" ' +
'--work-dir', '--preferred-challenges "dns,http" ' +
'/tmp/letsencrypt-lib', '--no-random-sleep-on-renew ' +
'--logs-dir', '--disable-hook-validation ' +
'/tmp/letsencrypt-log', (letsencryptStaging ? '--staging' : '');
'--cert-name',
`npm-${certificate.id}`,
'--preferred-challenges',
'dns,http',
'--no-random-sleep-on-renew',
'--disable-hook-validation',
];
const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider); logger.info('Command:', cmd);
args.push(...adds.args);
logger.info(`Command: ${certbotCommand} ${args ? args.join(' ') : ''}`); return utils.exec(cmd)
return utils.execFile(certbotCommand, args, adds.opts)
.then((result) => { .then((result) => {
logger.info(result); logger.info(result);
return result; return result;
@@ -1022,29 +992,26 @@ const internalCertificate = {
throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`); throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`);
} }
logger.info(`Renewing LetsEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); logger.info(`Renewing Let'sEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);
const args = [ let mainCmd = certbotCommand + ' renew --force-renewal ' +
'renew', '--config "' + letsencryptConfig + '" ' +
'--force-renewal', '--work-dir "/tmp/letsencrypt-lib" ' +
'--config', '--logs-dir "/tmp/letsencrypt-log" ' +
letsencryptConfig, '--cert-name "npm-' + certificate.id + '" ' +
'--work-dir', '--disable-hook-validation ' +
'/tmp/letsencrypt-lib', '--no-random-sleep-on-renew ' +
'--logs-dir', (letsencryptStaging ? ' --staging' : '');
'/tmp/letsencrypt-log',
'--cert-name',
`npm-${certificate.id}`,
'--disable-hook-validation',
'--no-random-sleep-on-renew',
];
const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider); // Prepend the path to the credentials file as an environment variable
args.push(...adds.args); if (certificate.meta.dns_provider === 'route53') {
const credentialsLocation = '/etc/letsencrypt/credentials/credentials-' + certificate.id;
mainCmd = 'AWS_CONFIG_FILE=\'' + credentialsLocation + '\' ' + mainCmd;
}
logger.info(`Command: ${certbotCommand} ${args ? args.join(' ') : ''}`); logger.info('Command:', mainCmd);
return utils.execFile(certbotCommand, args, adds.opts) return utils.exec(mainCmd)
.then(async (result) => { .then(async (result) => {
logger.info(result); logger.info(result);
return result; return result;
@@ -1057,29 +1024,24 @@ const internalCertificate = {
* @returns {Promise} * @returns {Promise}
*/ */
revokeLetsEncryptSsl: (certificate, throw_errors) => { revokeLetsEncryptSsl: (certificate, throw_errors) => {
logger.info(`Revoking LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`); logger.info('Revoking Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
const args = [ const mainCmd = certbotCommand + ' revoke ' +
'revoke', '--config "' + letsencryptConfig + '" ' +
'--config', '--work-dir "/tmp/letsencrypt-lib" ' +
letsencryptConfig, '--logs-dir "/tmp/letsencrypt-log" ' +
'--work-dir', '--cert-path "/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem" ' +
'/tmp/letsencrypt-lib', '--delete-after-revoke ' +
'--logs-dir', (letsencryptStaging ? '--staging' : '');
'/tmp/letsencrypt-log',
'--cert-path',
`${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`,
'--delete-after-revoke',
];
const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id); // Don't fail command if file does not exist
args.push(...adds.args); const delete_credentialsCmd = `rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`;
logger.info(`Command: ${certbotCommand} ${args ? args.join(' ') : ''}`); logger.info('Command:', mainCmd + '; ' + delete_credentialsCmd);
return utils.execFile(certbotCommand, args, adds.opts) return utils.exec(mainCmd)
.then(async (result) => { .then(async (result) => {
await utils.exec(`rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`); await utils.exec(delete_credentialsCmd);
logger.info(result); logger.info(result);
return result; return result;
}) })
@@ -1097,8 +1059,9 @@ const internalCertificate = {
* @returns {Boolean} * @returns {Boolean}
*/ */
hasLetsEncryptSslCerts: (certificate) => { hasLetsEncryptSslCerts: (certificate) => {
const letsencryptPath = internalCertificate.getLiveCertPath(certificate.id); const letsencryptPath = '/etc/letsencrypt/live/npm-' + certificate.id;
return fs.existsSync(`${letsencryptPath}/fullchain.pem`) && fs.existsSync(`${letsencryptPath}/privkey.pem`);
return fs.existsSync(letsencryptPath + '/fullchain.pem') && fs.existsSync(letsencryptPath + '/privkey.pem');
}, },
/** /**
@@ -1110,7 +1073,7 @@ const internalCertificate = {
*/ */
disableInUseHosts: (in_use_result) => { disableInUseHosts: (in_use_result) => {
if (in_use_result.total_count) { if (in_use_result.total_count) {
const promises = []; let promises = [];
if (in_use_result.proxy_hosts.length) { if (in_use_result.proxy_hosts.length) {
promises.push(internalNginx.bulkDeleteConfigs('proxy_host', in_use_result.proxy_hosts)); promises.push(internalNginx.bulkDeleteConfigs('proxy_host', in_use_result.proxy_hosts));
@@ -1140,7 +1103,7 @@ const internalCertificate = {
*/ */
enableInUseHosts: (in_use_result) => { enableInUseHosts: (in_use_result) => {
if (in_use_result.total_count) { if (in_use_result.total_count) {
const promises = []; let promises = [];
if (in_use_result.proxy_hosts.length) { if (in_use_result.proxy_hosts.length) {
promises.push(internalNginx.bulkGenerateConfigs('proxy_host', in_use_result.proxy_hosts)); promises.push(internalNginx.bulkGenerateConfigs('proxy_host', in_use_result.proxy_hosts));
@@ -1173,12 +1136,12 @@ const internalCertificate = {
// Create a test challenge file // Create a test challenge file
const testChallengeDir = '/data/letsencrypt-acme-challenge/.well-known/acme-challenge'; const testChallengeDir = '/data/letsencrypt-acme-challenge/.well-known/acme-challenge';
const testChallengeFile = `${testChallengeDir}/test-challenge`; const testChallengeFile = testChallengeDir + '/test-challenge';
fs.mkdirSync(testChallengeDir, {recursive: true}); fs.mkdirSync(testChallengeDir, {recursive: true});
fs.writeFileSync(testChallengeFile, 'Success', {encoding: 'utf8'}); fs.writeFileSync(testChallengeFile, 'Success', {encoding: 'utf8'});
async function performTestForDomain (domain) { async function performTestForDomain (domain) {
logger.info(`Testing http challenge for ${domain}`); logger.info('Testing http challenge for ' + domain);
const url = `http://${domain}/.well-known/acme-challenge/test-challenge`; const url = `http://${domain}/.well-known/acme-challenge/test-challenge`;
const formBody = `method=G&url=${encodeURI(url)}&bodytype=T&requestbody=&headername=User-Agent&headervalue=None&locationid=1&ch=false&cc=false`; const formBody = `method=G&url=${encodeURI(url)}&bodytype=T&requestbody=&headername=User-Agent&headervalue=None&locationid=1&ch=false&cc=false`;
const options = { const options = {
@@ -1192,16 +1155,13 @@ const internalCertificate = {
const result = await new Promise((resolve) => { const result = await new Promise((resolve) => {
const req = https.request('https://www.site24x7.com/tools/restapi-tester', options, (res) => { const req = https.request('https://www.site24x7.com/tools/restapi-tester', options, function (res) {
let responseBody = ''; let responseBody = '';
res.on('data', (chunk) => { res.on('data', (chunk) => responseBody = responseBody + chunk);
responseBody = responseBody + chunk; res.on('end', function () {
});
res.on('end', () => {
try { 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} because HTTP status code ${res.statusCode} was returned: ${parsedBody.message}`);
resolve(undefined); resolve(undefined);
@@ -1222,7 +1182,7 @@ const internalCertificate = {
// Make sure to write the request body. // Make sure to write the request body.
req.write(formBody); req.write(formBody);
req.end(); req.end();
req.on('error', (e) => { logger.warn(`Failed to test HTTP challenge for domain ${domain}`, e); req.on('error', function (e) { logger.warn(`Failed to test HTTP challenge for domain ${domain}`, e);
resolve(undefined); }); resolve(undefined); });
}); });
@@ -1264,34 +1224,6 @@ const internalCertificate = {
fs.unlinkSync(testChallengeFile); fs.unlinkSync(testChallengeFile);
return results; return results;
},
getAdditionalCertbotArgs: (certificate_id, dns_provider) => {
const args = [];
if (letsencryptServer !== null) {
args.push('--server', letsencryptServer);
}
if (letsencryptStaging && letsencryptServer === null) {
args.push('--staging');
}
// For route53, add the credentials file as an environment variable,
// inheriting the process env
const opts = {};
if (certificate_id && dns_provider === 'route53') {
opts.env = process.env;
opts.env.AWS_CONFIG_FILE = `/etc/letsencrypt/credentials/credentials-${certificate_id}`;
}
if (dns_provider === 'duckdns') {
args.push('--dns-duckdns-no-txt-restore');
}
return {args: args, opts: opts};
},
getLiveCertPath: (certificate_id) => {
return `/etc/letsencrypt/live/npm-${certificate_id}`;
} }
}; };

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

@@ -1,5 +1,5 @@
const _ = require('lodash'); const _ = require('lodash');
const fs = require('node:fs'); const fs = require('fs');
const logger = require('../logger').nginx; const logger = require('../logger').nginx;
const config = require('../lib/config'); const config = require('../lib/config');
const utils = require('../lib/utils'); const utils = require('../lib/utils');
@@ -57,9 +57,9 @@ const internalNginx = {
// It will always look like this: // It will always look like this:
// nginx: [alert] could not open error log file: open() "/var/log/nginx/error.log" failed (6: No such device or address) // nginx: [alert] could not open error log file: open() "/var/log/nginx/error.log" failed (6: No such device or address)
const valid_lines = []; let valid_lines = [];
const err_lines = err.message.split('\n'); let err_lines = err.message.split('\n');
err_lines.map((line) => { err_lines.map(function (line) {
if (line.indexOf('/var/log/nginx/error.log') === -1) { if (line.indexOf('/var/log/nginx/error.log') === -1) {
valid_lines.push(line); valid_lines.push(line);
} }
@@ -105,7 +105,7 @@ const internalNginx = {
logger.info('Testing Nginx configuration'); logger.info('Testing Nginx configuration');
} }
return utils.execFile('/usr/sbin/nginx', ['-t', '-g', 'error_log off;']); return utils.exec('/usr/sbin/nginx -t -g "error_log off;"');
}, },
/** /**
@@ -115,7 +115,7 @@ const internalNginx = {
return internalNginx.test() return internalNginx.test()
.then(() => { .then(() => {
logger.info('Reloading Nginx'); logger.info('Reloading Nginx');
return utils.execFile('/usr/sbin/nginx', ['-s', 'reload']); return utils.exec('/usr/sbin/nginx -s reload');
}); });
}, },
@@ -128,7 +128,7 @@ const internalNginx = {
if (host_type === 'default') { if (host_type === 'default') {
return '/data/nginx/default_host/site.conf'; return '/data/nginx/default_host/site.conf';
} }
return `/data/nginx/${internalNginx.getFileFriendlyHostType(host_type)}/${host_id}.conf`; return '/data/nginx/' + internalNginx.getFileFriendlyHostType(host_type) + '/' + host_id + '.conf';
}, },
/** /**
@@ -141,7 +141,7 @@ const internalNginx = {
let template; let template;
try { try {
template = fs.readFileSync(`${__dirname}/../templates/_location.conf`, {encoding: 'utf8'}); template = fs.readFileSync(__dirname + '/../templates/_location.conf', {encoding: 'utf8'});
} catch (err) { } catch (err) {
reject(new error.ConfigurationError(err.message)); reject(new error.ConfigurationError(err.message));
return; return;
@@ -152,7 +152,7 @@ const internalNginx = {
const locationRendering = async () => { const locationRendering = async () => {
for (let i = 0; i < host.locations.length; i++) { for (let i = 0; i < host.locations.length; i++) {
const locationCopy = Object.assign({}, {access_list_id: host.access_list_id}, {certificate_id: host.certificate_id}, let locationCopy = Object.assign({}, {access_list_id: host.access_list_id}, {certificate_id: host.certificate_id},
{ssl_forced: host.ssl_forced}, {caching_enabled: host.caching_enabled}, {block_exploits: host.block_exploits}, {ssl_forced: host.ssl_forced}, {caching_enabled: host.caching_enabled}, {block_exploits: host.block_exploits},
{allow_websocket_upgrade: host.allow_websocket_upgrade}, {http2_support: host.http2_support}, {allow_websocket_upgrade: host.allow_websocket_upgrade}, {http2_support: host.http2_support},
{hsts_enabled: host.hsts_enabled}, {hsts_subdomains: host.hsts_subdomains}, {access_list: host.access_list}, {hsts_enabled: host.hsts_enabled}, {hsts_subdomains: host.hsts_subdomains}, {access_list: host.access_list},
@@ -181,23 +181,21 @@ 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:
const 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()) {
logger.info(`Generating ${nice_host_type} Config:`, JSON.stringify(host, null, 2)); logger.info('Generating ' + nice_host_type + ' Config:', JSON.stringify(host, null, 2));
} }
const renderEngine = utils.getRenderEngine(); const renderEngine = utils.getRenderEngine();
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let template = null; let template = null;
const filename = internalNginx.getConfigName(nice_host_type, host.id); let filename = internalNginx.getConfigName(nice_host_type, host.id);
try { try {
template = fs.readFileSync(`${__dirname}/../templates/${nice_host_type}.conf`, {encoding: 'utf8'}); template = fs.readFileSync(__dirname + '/../templates/' + nice_host_type + '.conf', {encoding: 'utf8'});
} catch (err) { } catch (err) {
reject(new error.ConfigurationError(err.message)); reject(new error.ConfigurationError(err.message));
return; return;
@@ -252,7 +250,7 @@ const internalNginx = {
}) })
.catch((err) => { .catch((err) => {
if (config.debug()) { if (config.debug()) {
logger.warn(`Could not write ${filename}:`, err.message); logger.warn('Could not write ' + filename + ':', err.message);
} }
reject(new error.ConfigurationError(err.message)); reject(new error.ConfigurationError(err.message));
@@ -278,10 +276,10 @@ const internalNginx = {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let template = null; let template = null;
const filename = `/data/nginx/temp/letsencrypt_${certificate.id}.conf`; let filename = '/data/nginx/temp/letsencrypt_' + certificate.id + '.conf';
try { try {
template = fs.readFileSync(`${__dirname}/../templates/letsencrypt-request.conf`, {encoding: 'utf8'}); template = fs.readFileSync(__dirname + '/../templates/letsencrypt-request.conf', {encoding: 'utf8'});
} catch (err) { } catch (err) {
reject(new error.ConfigurationError(err.message)); reject(new error.ConfigurationError(err.message));
return; return;
@@ -302,7 +300,7 @@ const internalNginx = {
}) })
.catch((err) => { .catch((err) => {
if (config.debug()) { if (config.debug()) {
logger.warn(`Could not write ${filename}:`, err.message); logger.warn('Could not write ' + filename + ':', err.message);
} }
reject(new error.ConfigurationError(err.message)); reject(new error.ConfigurationError(err.message));
@@ -316,7 +314,7 @@ const internalNginx = {
* @param {String} filename * @param {String} filename
*/ */
deleteFile: (filename) => { deleteFile: (filename) => {
logger.debug(`Deleting file: ${filename}`); logger.debug('Deleting file: ' + filename);
try { try {
fs.unlinkSync(filename); fs.unlinkSync(filename);
} catch (err) { } catch (err) {
@@ -330,7 +328,7 @@ const internalNginx = {
* @returns String * @returns String
*/ */
getFileFriendlyHostType: (host_type) => { getFileFriendlyHostType: (host_type) => {
return host_type.replace(/-/g, '_'); return host_type.replace(new RegExp('-', 'g'), '_');
}, },
/** /**
@@ -340,7 +338,7 @@ const internalNginx = {
* @returns {Promise} * @returns {Promise}
*/ */
deleteLetsEncryptRequestConfig: (certificate) => { deleteLetsEncryptRequestConfig: (certificate) => {
const config_file = `/data/nginx/temp/letsencrypt_${certificate.id}.conf`; const config_file = '/data/nginx/temp/letsencrypt_' + certificate.id + '.conf';
return new Promise((resolve/*, reject*/) => { return new Promise((resolve/*, reject*/) => {
internalNginx.deleteFile(config_file); internalNginx.deleteFile(config_file);
resolve(); resolve();
@@ -355,7 +353,7 @@ const internalNginx = {
*/ */
deleteConfig: (host_type, host, delete_err_file) => { deleteConfig: (host_type, host, delete_err_file) => {
const config_file = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id); const config_file = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id);
const config_file_err = `${config_file}.err`; const config_file_err = config_file + '.err';
return new Promise((resolve/*, reject*/) => { return new Promise((resolve/*, reject*/) => {
internalNginx.deleteFile(config_file); internalNginx.deleteFile(config_file);
@@ -373,7 +371,7 @@ const internalNginx = {
*/ */
renameConfigAsError: (host_type, host) => { renameConfigAsError: (host_type, host) => {
const config_file = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id); const config_file = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id);
const config_file_err = `${config_file}.err`; const config_file_err = config_file + '.err';
return new Promise((resolve/*, reject*/) => { return new Promise((resolve/*, reject*/) => {
fs.unlink(config_file, () => { fs.unlink(config_file, () => {
@@ -392,8 +390,8 @@ const internalNginx = {
* @returns {Promise} * @returns {Promise}
*/ */
bulkGenerateConfigs: (host_type, hosts) => { bulkGenerateConfigs: (host_type, hosts) => {
const promises = []; let promises = [];
hosts.map((host) => { hosts.map(function (host) {
promises.push(internalNginx.generateConfig(host_type, host)); promises.push(internalNginx.generateConfig(host_type, host));
}); });
@@ -406,8 +404,8 @@ const internalNginx = {
* @returns {Promise} * @returns {Promise}
*/ */
bulkDeleteConfigs: (host_type, hosts) => { bulkDeleteConfigs: (host_type, hosts) => {
const promises = []; let promises = [];
hosts.map((host) => { hosts.map(function (host) {
promises.push(internalNginx.deleteConfig(host_type, host, true)); promises.push(internalNginx.deleteConfig(host_type, host, true));
}); });
@@ -418,12 +416,14 @@ const internalNginx = {
* @param {string} config * @param {string} config
* @returns {boolean} * @returns {boolean}
*/ */
advancedConfigHasDefaultLocation: (cfg) => !!cfg.match(/^(?:.*;)?\s*?location\s*?\/\s*?{/im), advancedConfigHasDefaultLocation: function (cfg) {
return !!cfg.match(/^(?:.*;)?\s*?location\s*?\/\s*?{/im);
},
/** /**
* @returns {boolean} * @returns {boolean}
*/ */
ipv6Enabled: () => { ipv6Enabled: function () {
if (typeof process.env.DISABLE_IPV6 !== 'undefined') { if (typeof process.env.DISABLE_IPV6 !== 'undefined') {
const disabled = process.env.DISABLE_IPV6.toLowerCase(); const disabled = process.env.DISABLE_IPV6.toLowerCase();
return !(disabled === 'on' || disabled === 'true' || disabled === '1' || disabled === 'yes'); return !(disabled === 'on' || disabled === 'true' || disabled === '1' || disabled === 'yes');

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)
@@ -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,11 +293,11 @@ 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]')
.orderBy('incoming_port', 'ASC'); .orderBy('incoming_port', 'ASC');
if (access_data.permission_visibility !== 'all') { if (access_data.permission_visibility !== 'all') {
@@ -376,9 +305,9 @@ const internalStream = {
} }
// 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

@@ -11,7 +11,7 @@ const certbot = {
/** /**
* @param {array} pluginKeys * @param {array} pluginKeys
*/ */
installPlugins: async (pluginKeys) => { installPlugins: async function (pluginKeys) {
let hasErrors = false; let hasErrors = false;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@@ -21,7 +21,7 @@ const certbot = {
} }
batchflow(pluginKeys).sequential() batchflow(pluginKeys).sequential()
.each((_i, pluginKey, next) => { .each((i, pluginKey, next) => {
certbot.installPlugin(pluginKey) certbot.installPlugin(pluginKey)
.then(() => { .then(() => {
next(); next();
@@ -51,7 +51,7 @@ const certbot = {
* @param {string} pluginKey * @param {string} pluginKey
* @returns {Object} * @returns {Object}
*/ */
installPlugin: async (pluginKey) => { installPlugin: async function (pluginKey) {
if (typeof dnsPlugins[pluginKey] === 'undefined') { if (typeof dnsPlugins[pluginKey] === 'undefined') {
// throw Error(`Certbot plugin ${pluginKey} not found`); // throw Error(`Certbot plugin ${pluginKey} not found`);
throw new error.ItemNotFoundError(pluginKey); throw new error.ItemNotFoundError(pluginKey);
@@ -63,15 +63,8 @@ const certbot = {
plugin.version = plugin.version.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT); plugin.version = plugin.version.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT);
plugin.dependencies = plugin.dependencies.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT); plugin.dependencies = plugin.dependencies.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT);
// SETUPTOOLS_USE_DISTUTILS is required for certbot plugins to install correctly const cmd = '. /opt/certbot/bin/activate && pip install --no-cache-dir ' + plugin.dependencies + ' ' + plugin.package_name + plugin.version + ' ' + ' && deactivate';
// in new versions of Python return utils.exec(cmd)
let env = Object.assign({}, process.env, {SETUPTOOLS_USE_DISTUTILS: 'stdlib'});
if (typeof plugin.env === 'object') {
env = Object.assign(env, plugin.env);
}
const cmd = `. /opt/certbot/bin/activate && pip install --no-cache-dir ${plugin.dependencies} ${plugin.package_name}${plugin.version} && deactivate`;
return utils.exec(cmd, {env})
.then((result) => { .then((result) => {
logger.complete(`Installed ${pluginKey}`); logger.complete(`Installed ${pluginKey}`);
return result; return result;

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
}, },
@@ -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

@@ -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

@@ -1,13 +1,13 @@
const _ = require('lodash'); const _ = require('lodash');
const exec = require('node:child_process').exec; const exec = require('child_process').exec;
const execFile = require('node: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'); const error = require('./error');
module.exports = { module.exports = {
exec: async (cmd, options = {}) => { exec: async function(cmd, options = {}) {
logger.debug('CMD:', cmd); logger.debug('CMD:', cmd);
const { stdout, stderr } = await new Promise((resolve, reject) => { const { stdout, stderr } = await new Promise((resolve, reject) => {
@@ -29,19 +29,15 @@ module.exports = {
/** /**
* @param {String} cmd * @param {String} cmd
* @param {Array} args * @param {Array} args
* @param {Object|undefined} options
* @returns {Promise} * @returns {Promise}
*/ */
execFile: (cmd, args, options) => { execFile: function (cmd, args) {
logger.debug(`CMD: ${cmd} ${args ? args.join(' ') : ''}`); // logger.debug('CMD: ' + cmd + ' ' + (args ? args.join(' ') : ''));
if (typeof options === 'undefined') {
options = {};
}
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
execFile(cmd, args, options, (err, stdout, stderr) => { execFile(cmd, args, function (err, stdout, /*stderr*/) {
if (err && typeof err === 'object') { if (err && typeof err === 'object') {
reject(new error.CommandError(stderr, 1, err)); reject(err);
} else { } else {
resolve(stdout.trim()); resolve(stdout.trim());
} }
@@ -55,7 +51,7 @@ module.exports = {
* @param {Array} omissions * @param {Array} omissions
* @returns {Function} * @returns {Function}
*/ */
omitRow: (omissions) => { omitRow: function (omissions) {
/** /**
* @param {Object} row * @param {Object} row
* @returns {Object} * @returns {Object}
@@ -71,7 +67,7 @@ module.exports = {
* @param {Array} omissions * @param {Array} omissions
* @returns {Function} * @returns {Function}
*/ */
omitRows: (omissions) => { omitRows: function (omissions) {
/** /**
* @param {Array} rows * @param {Array} rows
* @returns {Object} * @returns {Object}
@@ -87,9 +83,9 @@ module.exports = {
/** /**
* @returns {Object} Liquid render engine * @returns {Object} Liquid render engine
*/ */
getRenderEngine: () => { getRenderEngine: function () {
const renderEngine = new Liquid({ const renderEngine = new Liquid({
root: `${__dirname}/../templates/` root: __dirname + '/../templates/'
}); });
/** /**

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

@@ -0,0 +1,48 @@
const migrate_name = 'openid_connect';
const logger = require('../logger').migrate;
/**
* Migrate
*
* @see http://knexjs.org/#Schema
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.up = function (knex/*, Promise*/) {
logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema.table('proxy_host', function (proxy_host) {
proxy_host.integer('openidc_enabled').notNull().unsigned().defaultTo(0);
proxy_host.text('openidc_redirect_uri').notNull().defaultTo('');
proxy_host.text('openidc_discovery').notNull().defaultTo('');
proxy_host.text('openidc_auth_method').notNull().defaultTo('');
proxy_host.text('openidc_client_id').notNull().defaultTo('');
proxy_host.text('openidc_client_secret').notNull().defaultTo('');
})
.then(() => {
logger.info('[' + migrate_name + '] proxy_host Table altered');
});
};
/**
* Undo Migrate
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.down = function (knex/*, Promise*/) {
return knex.schema.table('proxy_host', function (proxy_host) {
proxy_host.dropColumn('openidc_enabled');
proxy_host.dropColumn('openidc_redirect_uri');
proxy_host.dropColumn('openidc_discovery');
proxy_host.dropColumn('openidc_auth_method');
proxy_host.dropColumn('openidc_client_id');
proxy_host.dropColumn('openidc_client_secret');
})
.then(() => {
logger.info('[' + migrate_name + '] proxy_host Table altered');
});
};

View File

@@ -0,0 +1,40 @@
const migrate_name = 'openid_allowed_users';
const logger = require('../logger').migrate;
/**
* Migrate
*
* @see http://knexjs.org/#Schema
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.up = function (knex/*, Promise*/) {
logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema.table('proxy_host', function (proxy_host) {
proxy_host.integer('openidc_restrict_users_enabled').notNull().unsigned().defaultTo(0);
proxy_host.json('openidc_allowed_users').notNull().defaultTo([]);
})
.then(() => {
logger.info('[' + migrate_name + '] proxy_host Table altered');
});
};
/**
* Undo Migrate
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.down = function (knex/*, Promise*/) {
return knex.schema.table('proxy_host', function (proxy_host) {
proxy_host.dropColumn('openidc_restrict_users_enabled');
proxy_host.dropColumn('openidc_allowed_users');
})
.then(() => {
logger.info('[' + migrate_name + '] proxy_host Table altered');
});
};

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();
@@ -33,12 +20,23 @@ class ProxyHost extends Model {
this.domain_names = []; this.domain_names = [];
} }
// Default for openidc_allowed_users
if (typeof this.openidc_allowed_users === 'undefined') {
this.openidc_allowed_users = [];
}
// Default for meta // Default for meta
if (typeof this.meta === 'undefined') { if (typeof this.meta === 'undefined') {
this.meta = {}; this.meta = {};
} }
// Openidc defaults
if (typeof this.openidc_auth_method === 'undefined') {
this.openidc_auth_method = 'client_secret_post';
}
this.domain_names.sort(); this.domain_names.sort();
this.openidc_allowed_users.sort();
} }
$beforeUpdate () { $beforeUpdate () {
@@ -48,16 +46,11 @@ class ProxyHost extends Model {
if (typeof this.domain_names !== 'undefined') { if (typeof this.domain_names !== 'undefined') {
this.domain_names.sort(); this.domain_names.sort();
} }
}
$parseDatabaseJson(json) { // Sort openidc_allowed_users
json = super.$parseDatabaseJson(json); if (typeof this.openidc_allowed_users !== 'undefined') {
return helpers.convertIntFieldsToBool(json, boolFields); this.openidc_allowed_users.sort();
} }
$formatDatabaseJson(json) {
json = helpers.convertBoolFieldsToInt(json, boolFields);
return super.$formatDatabaseJson(json);
} }
static get name () { static get name () {
@@ -69,7 +62,7 @@ class ProxyHost extends Model {
} }
static get jsonAttributes () { static get jsonAttributes () {
return ['domain_names', 'meta', 'locations']; return ['domain_names', 'meta', 'locations', 'openidc_allowed_users'];
} }
static get relationMappings () { static get relationMappings () {

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.19.2",
"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,12 +1,10 @@
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');
const router = express.Router({ let router = express.Router({
caseSensitive: true, caseSensitive: true,
strict: true, strict: true,
mergeParams: true mergeParams: true
@@ -17,7 +15,7 @@ const 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,22 +75,17 @@ router
*/ */
router router
.route('/test-http') .route('/test-http')
.options((_, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())
/** /**
* GET /api/nginx/certificates/test-http * GET /api/nginx/certificates/test-http
* *
* Test HTTP challenge for domains * Test HTTP challenge for domains
*/ */
.get((req, res, next) => { .get((req, res, next) => {
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())
@@ -231,7 +242,7 @@ router
*/ */
router router
.route('/:certificate_id/download') .route('/:certificate_id/download')
.options((_req, res) => { .options((req, res) => {
res.sendStatus(204); res.sendStatus(204);
}) })
.all(jwtdecode()) .all(jwtdecode())
@@ -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,13 +167,13 @@ 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);
}) })
.then((result) => { .then((result) => {
res.status(200) res.status(201)
.send(result); .send(result);
}) })
.catch(next); .catch(next);
@@ -206,13 +198,13 @@ router
* Set some or all permissions for a user * Set some or all permissions for a user
*/ */
.put((req, res, next) => { .put((req, res, next) => {
apiValidator(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);
}) })
.then((result) => { .then((result) => {
res.status(200) res.status(201)
.send(result); .send(result);
}) })
.catch(next); .catch(next);
@@ -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())
@@ -238,7 +230,7 @@ router
.post((req, res, next) => { .post((req, res, next) => {
internalUser.loginAs(res.locals.access, {id: parseInt(req.params.user_id, 10)}) internalUser.loginAs(res.locals.access, {id: parseInt(req.params.user_id, 10)})
.then((result) => { .then((result) => {
res.status(200) res.status(201)
.send(result); .send(result);
}) })
.catch(next); .catch(next);

View File

@@ -1,120 +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"
},
"email": {
"description": "Email address",
"type": "string",
"pattern": "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"
}
}
}

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": {
"$ref": "../common.json#/properties/email"
},
"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": "Streams list",
"items": {
"$ref": "./stream-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,277 @@
{
"$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": 100,
"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"
},
"openidc_enabled": {
"description": "Is OpenID Connect authentication enabled",
"example": true,
"type": "boolean"
},
"openidc_redirect_uri": {
"type": "string"
},
"openidc_discovery": {
"type": "string"
},
"openidc_auth_method": {
"type": "string",
"pattern": "^(client_secret_basic|client_secret_post)$"
},
"openidc_client_id": {
"type": "string"
},
"openidc_client_secret": {
"type": "string"
},
"openidc_restrict_users_enabled": {
"description": "Only allow a specific set of OpenID Connect emails to access the resource",
"example": true,
"type": "boolean"
},
"openidc_allowed_users": {
"type": "array",
"minItems": 0,
"items": {
"type": "string",
"description": "Email Address",
"example": "john@example.com",
"format": "email",
"minLength": 1
}
}
}
}

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,483 @@
{
"$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"
},
"openidc_enabled": {
"$ref": "../definitions.json#/definitions/openidc_enabled"
},
"openidc_redirect_uri": {
"$ref": "../definitions.json#/definitions/openidc_redirect_uri"
},
"openidc_discovery": {
"$ref": "../definitions.json#/definitions/openidc_discovery"
},
"openidc_auth_method": {
"$ref": "../definitions.json#/definitions/openidc_auth_method"
},
"openidc_client_id": {
"$ref": "../definitions.json#/definitions/openidc_client_id"
},
"openidc_client_secret": {
"$ref": "../definitions.json#/definitions/openidc_client_secret"
},
"openidc_restrict_users_enabled": {
"$ref": "../definitions.json#/definitions/openidc_restrict_users_enabled"
},
"openidc_allowed_users": {
"$ref": "../definitions.json#/definitions/openidc_allowed_users"
},
"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"
},
"openidc_enabled": {
"$ref": "#/definitions/openidc_enabled"
},
"openidc_redirect_uri": {
"$ref": "#/definitions/openidc_redirect_uri"
},
"openidc_discovery": {
"$ref": "#/definitions/openidc_discovery"
},
"openidc_auth_method": {
"$ref": "#/definitions/openidc_auth_method"
},
"openidc_client_id": {
"$ref": "#/definitions/openidc_client_id"
},
"openidc_client_secret": {
"$ref": "#/definitions/openidc_client_secret"
},
"openidc_restrict_users_enabled": {
"$ref": "#/definitions/openidc_restrict_users_enabled"
},
"openidc_allowed_users": {
"$ref": "#/definitions/openidc_allowed_users"
},
"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"
},
"openidc_enabled": {
"$ref": "#/definitions/openidc_enabled"
},
"openidc_redirect_uri": {
"$ref": "#/definitions/openidc_redirect_uri"
},
"openidc_discovery": {
"$ref": "#/definitions/openidc_discovery"
},
"openidc_auth_method": {
"$ref": "#/definitions/openidc_auth_method"
},
"openidc_client_id": {
"$ref": "#/definitions/openidc_client_id"
},
"openidc_client_secret": {
"$ref": "#/definitions/openidc_client_secret"
},
"openidc_restrict_users_enabled": {
"$ref": "#/definitions/openidc_restrict_users_enabled"
},
"openidc_allowed_users": {
"$ref": "#/definitions/openidc_allowed_users"
},
"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"
},
"openidc_enabled": {
"$ref": "#/definitions/openidc_enabled"
},
"openidc_redirect_uri": {
"$ref": "#/definitions/openidc_redirect_uri"
},
"openidc_discovery": {
"$ref": "#/definitions/openidc_discovery"
},
"openidc_auth_method": {
"$ref": "#/definitions/openidc_auth_method"
},
"openidc_client_id": {
"$ref": "#/definitions/openidc_client_id"
},
"openidc_client_secret": {
"$ref": "#/definitions/openidc_client_secret"
},
"openidc_restrict_users_enabled": {
"$ref": "#/definitions/openidc_restrict_users_enabled"
},
"openidc_allowed_users": {
"$ref": "#/definitions/openidc_allowed_users"
},
"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"
}
}
}
}
}
}

View File

@@ -1,39 +0,0 @@
{
"operationId": "deleteCertificate",
"summary": "Delete a Certificate",
"tags": ["Certificates"],
"security": [
{
"BearerAuth": ["certificates"]
}
],
"parameters": [
{
"in": "path",
"name": "certID",
"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,35 +0,0 @@
{
"operationId": "downloadCertificate",
"summary": "Downloads a Certificate",
"tags": ["Certificates"],
"security": [
{
"BearerAuth": ["certificates"]
}
],
"parameters": [
{
"in": "path",
"name": "certID",
"schema": {
"type": "integer",
"minimum": 1
},
"required": true,
"example": 1
}
],
"responses": {
"200": {
"description": "200 response",
"content": {
"application/zip": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
}
}
}

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