Compare commits

..

12 Commits

Author SHA1 Message Date
c2965789a0 Bump freedns plugin 2024-10-17 12:57:49 +10:00
d499e2bfef Push PR and github branch builds to separate docker image
All checks were successful
Close stale issues and PRs / stale (push) Successful in 4s
2024-10-17 10:00:12 +10:00
2f9e062718 bump version 2024-10-17 09:05:25 +10:00
edbed1af90 Adds tests for settings endpoints
and reenables dns cert test
and fixes problems with schema
2024-10-17 08:48:47 +10:00
8497022e41 Merge pull request #4076 from Nephiel/4074-fix-1
All checks were successful
Close stale issues and PRs / stale (push) Successful in 5s
Fix schema validation errors
2024-10-17 07:07:05 +10:00
fa2c814fcb Fix schema validation in Default Site
Should solve error `data/value must match exactly one schema in oneOf` when setting the Default Site to 404 or 444. #4074
2024-10-16 19:09:14 +00:00
d96a3987c0 Fix forward_scheme validation in Redirection Host
Should solve error `data/forward_scheme must be equal to one of the allowed values` when configuring a Redirection Host with scheme set to `auto`. #4074
2024-10-16 19:04:50 +00:00
fe2d8895d6 Cypress test for http and dns cert provision 2024-10-16 14:53:57 +10:00
5bdc05878f Fix issues with certbot command when using LE_SERVER 2024-10-16 11:23:58 +10:00
929ac3bd7c Adds env var to set certbot acme server
this is required for test suite to use dns certbot request
without talking to live or staging letsencrypt servers or
production level dns providers. This is a backwards port
from the v3 branch and opens the door for a full certificate
cypress test
2024-10-16 11:06:29 +10:00
f48e1b46a8 Updated swagger cypress package,
which works with proxies
2024-10-16 08:32:49 +10:00
351ba8dacd More tests for certificates, fixed schema problems 2024-10-16 08:32:49 +10:00
45 changed files with 591 additions and 406 deletions

View File

@ -1 +1 @@
2.12.0 2.12.1

20
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/jc21/${IMAGE}:github-${BRANCH_LOWER}" buildxPushTags = "-t docker.io/nginxproxymanager/${IMAGE}-dev:${BRANCH_LOWER}"
} }
} }
} }
@ -127,6 +127,11 @@ 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: 'testing/results') {
archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml')
}
}
} }
} }
stage('Test Mysql') { stage('Test Mysql') {
@ -155,6 +160,11 @@ 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: 'testing/results') {
archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml')
}
}
} }
} }
stage('MultiArch Build') { stage('MultiArch Build') {
@ -193,7 +203,13 @@ pipeline {
} }
steps { steps {
script { script {
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) npmGithubPrComment("""Docker Image for build ${BUILD_NUMBER} is available on
[DockerHub](https://cloud.docker.com/repository/docker/nginxproxymanager/${IMAGE}-dev)
as `nginxproxymanager/${IMAGE}-dev:${BRANCH_LOWER}`
**Note:** ensure you backup your NPM instance before testing this image! Especially if there are database changes
**Note:** this is a different docker image namespace than the official image
""", true)
} }
} }
} }

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.0-green.svg?style=for-the-badge"> <img src="https://img.shields.io/badge/version-2.12.1-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>

View File

@ -9,22 +9,6 @@ function generateDbConfig() {
if (cfg.engine === 'knex-native') { if (cfg.engine === 'knex-native') {
return cfg.knex; return cfg.knex;
} }
if (cfg.engine === 'pg') {
return {
client: cfg.engine,
connection: {
host: cfg.host,
user: cfg.user,
password: cfg.password,
database: cfg.name,
port: cfg.port
},
migrations: {
tableName: 'migrations'
}
};
}
return { return {
client: cfg.engine, client: cfg.engine,
connection: { connection: {

View File

@ -3,27 +3,29 @@ const fs = require('fs');
const https = require('https'); const https = require('https');
const tempWrite = require('temp-write'); const tempWrite = require('temp-write');
const moment = require('moment'); const moment = require('moment');
const archiver = require('archiver');
const path = require('path');
const { isArray } = require('lodash');
const logger = require('../logger').ssl; const logger = require('../logger').ssl;
const config = require('../lib/config'); const config = require('../lib/config');
const error = require('../lib/error'); const error = require('../lib/error');
const utils = require('../lib/utils'); const utils = require('../lib/utils');
const certbot = require('../lib/certbot');
const certificateModel = require('../models/certificate'); const certificateModel = require('../models/certificate');
const tokenModel = require('../models/token'); const 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']; return ['is_deleted', 'owner.is_deleted'];
} }
const internalCertificate = { const internalCertificate = {
@ -207,6 +209,7 @@ const internalCertificate = {
.patchAndFetchById(certificate.id, { .patchAndFetchById(certificate.id, {
expires_on: moment(cert_info.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss') expires_on: moment(cert_info.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss')
}) })
.then(utils.omitRow(omissions()))
.then((saved_row) => { .then((saved_row) => {
// Add cert data for audit log // Add cert data for audit log
saved_row.meta = _.assign({}, saved_row.meta, { saved_row.meta = _.assign({}, saved_row.meta, {
@ -730,29 +733,29 @@ const internalCertificate = {
return utils.exec('openssl x509 -in ' + certificate_file + ' -subject -noout') return utils.exec('openssl x509 -in ' + certificate_file + ' -subject -noout')
.then((result) => { .then((result) => {
// Examples:
// subject=CN = *.jc21.com
// subject=CN = something.example.com // subject=CN = something.example.com
const regex = /(?:subject=)?[^=]+=\s+(\S+)/gim; const regex = /(?:subject=)?[^=]+=\s+(\S+)/gim;
const match = regex.exec(result); const match = regex.exec(result);
if (match && typeof match[1] !== 'undefined') {
if (typeof match[1] === 'undefined') {
throw new error.ValidationError('Could not determine subject from certificate: ' + result);
}
certData['cn'] = match[1]; certData['cn'] = match[1];
}
}) })
.then(() => { .then(() => {
return utils.exec('openssl x509 -in ' + certificate_file + ' -issuer -noout'); return utils.exec('openssl x509 -in ' + certificate_file + ' -issuer -noout');
}) })
.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') {
if (typeof match[1] === 'undefined') {
throw new error.ValidationError('Could not determine issuer from certificate: ' + result);
}
certData['issuer'] = match[1]; certData['issuer'] = match[1];
}
}) })
.then(() => { .then(() => {
return utils.exec('openssl x509 -in ' + certificate_file + ' -dates -noout'); return utils.exec('openssl x509 -in ' + certificate_file + ' -dates -noout');
@ -827,17 +830,18 @@ const internalCertificate = {
requestLetsEncryptSsl: (certificate) => { requestLetsEncryptSsl: (certificate) => {
logger.info('Requesting Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', ')); logger.info('Requesting Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
const cmd = certbotCommand + ' certonly ' + const cmd = `${certbotCommand} certonly ` +
'--config "' + letsencryptConfig + '" ' + `--config '${letsencryptConfig}' ` +
'--work-dir "/tmp/letsencrypt-lib" ' + '--work-dir "/tmp/letsencrypt-lib" ' +
'--logs-dir "/tmp/letsencrypt-log" ' + '--logs-dir "/tmp/letsencrypt-log" ' +
'--cert-name "npm-' + certificate.id + '" ' + `--cert-name "npm-${certificate.id}" ` +
'--agree-tos ' + '--agree-tos ' +
'--authenticator webroot ' + '--authenticator webroot ' +
'--email "' + certificate.meta.letsencrypt_email + '" ' + `--email '${certificate.meta.letsencrypt_email}' ` +
'--preferred-challenges "dns,http" ' + '--preferred-challenges "dns,http" ' +
'--domains "' + certificate.domain_names.join(',') + '" ' + `--domains "${certificate.domain_names.join(',')}" ` +
(letsencryptStaging ? '--staging' : ''); (letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') +
(letsencryptStaging && letsencryptServer === null ? '--staging ' : '');
logger.info('Command:', cmd); logger.info('Command:', cmd);
@ -868,25 +872,26 @@ const internalCertificate = {
const hasConfigArg = certificate.meta.dns_provider !== 'route53'; const hasConfigArg = certificate.meta.dns_provider !== 'route53';
let mainCmd = certbotCommand + ' certonly ' + let mainCmd = certbotCommand + ' certonly ' +
'--config "' + letsencryptConfig + '" ' + `--config '${letsencryptConfig}' ` +
'--work-dir "/tmp/letsencrypt-lib" ' + '--work-dir "/tmp/letsencrypt-lib" ' +
'--logs-dir "/tmp/letsencrypt-log" ' + '--logs-dir "/tmp/letsencrypt-log" ' +
'--cert-name "npm-' + certificate.id + '" ' + `--cert-name 'npm-${certificate.id}' ` +
'--agree-tos ' + '--agree-tos ' +
'--email "' + certificate.meta.letsencrypt_email + '" ' + `--email '${certificate.meta.letsencrypt_email}' ` +
'--domains "' + certificate.domain_names.join(',') + '" ' + `--domains '${certificate.domain_names.join(',')}' ` +
'--authenticator ' + dnsPlugin.full_plugin_name + ' ' + `--authenticator '${dnsPlugin.full_plugin_name}' ` +
( (
hasConfigArg hasConfigArg
? '--' + dnsPlugin.full_plugin_name + '-credentials "' + credentialsLocation + '"' ? `--${dnsPlugin.full_plugin_name}-credentials '${credentialsLocation}' `
: '' : ''
) + ) +
( (
certificate.meta.propagation_seconds !== undefined certificate.meta.propagation_seconds !== undefined
? ' --' + dnsPlugin.full_plugin_name + '-propagation-seconds ' + certificate.meta.propagation_seconds ? `--${dnsPlugin.full_plugin_name}-propagation-seconds '${certificate.meta.propagation_seconds}' `
: '' : ''
) + ) +
(letsencryptStaging ? ' --staging' : ''); (letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') +
(letsencryptStaging && letsencryptServer === null ? '--staging ' : '');
// Prepend the path to the credentials file as an environment variable // Prepend the path to the credentials file as an environment variable
if (certificate.meta.dns_provider === 'route53') { if (certificate.meta.dns_provider === 'route53') {
@ -963,14 +968,15 @@ const internalCertificate = {
logger.info('Renewing Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', ')); logger.info('Renewing Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
const cmd = certbotCommand + ' renew --force-renewal ' + const cmd = certbotCommand + ' renew --force-renewal ' +
'--config "' + letsencryptConfig + '" ' + `--config '${letsencryptConfig}' ` +
'--work-dir "/tmp/letsencrypt-lib" ' + '--work-dir "/tmp/letsencrypt-lib" ' +
'--logs-dir "/tmp/letsencrypt-log" ' + '--logs-dir "/tmp/letsencrypt-log" ' +
'--cert-name "npm-' + certificate.id + '" ' + `--cert-name 'npm-${certificate.id}' ` +
'--preferred-challenges "dns,http" ' + '--preferred-challenges "dns,http" ' +
'--no-random-sleep-on-renew ' + '--no-random-sleep-on-renew ' +
'--disable-hook-validation ' + '--disable-hook-validation ' +
(letsencryptStaging ? '--staging' : ''); (letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') +
(letsencryptStaging && letsencryptServer === null ? '--staging ' : '');
logger.info('Command:', cmd); logger.info('Command:', cmd);
@ -995,13 +1001,14 @@ const internalCertificate = {
logger.info(`Renewing Let'sEncrypt 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(', ')}`);
let mainCmd = certbotCommand + ' renew --force-renewal ' + let mainCmd = certbotCommand + ' renew --force-renewal ' +
'--config "' + letsencryptConfig + '" ' + `--config "${letsencryptConfig}" ` +
'--work-dir "/tmp/letsencrypt-lib" ' + '--work-dir "/tmp/letsencrypt-lib" ' +
'--logs-dir "/tmp/letsencrypt-log" ' + '--logs-dir "/tmp/letsencrypt-log" ' +
'--cert-name "npm-' + certificate.id + '" ' + `--cert-name 'npm-${certificate.id}' ` +
'--disable-hook-validation ' + '--disable-hook-validation ' +
'--no-random-sleep-on-renew ' + '--no-random-sleep-on-renew ' +
(letsencryptStaging ? ' --staging' : ''); (letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') +
(letsencryptStaging && letsencryptServer === null ? '--staging ' : '');
// Prepend the path to the credentials file as an environment variable // Prepend the path to the credentials file as an environment variable
if (certificate.meta.dns_provider === 'route53') { if (certificate.meta.dns_provider === 'route53') {
@ -1027,12 +1034,13 @@ const internalCertificate = {
logger.info('Revoking Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', ')); logger.info('Revoking Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
const mainCmd = certbotCommand + ' revoke ' + const mainCmd = certbotCommand + ' revoke ' +
'--config "' + letsencryptConfig + '" ' + `--config '${letsencryptConfig}' ` +
'--work-dir "/tmp/letsencrypt-lib" ' + '--work-dir "/tmp/letsencrypt-lib" ' +
'--logs-dir "/tmp/letsencrypt-log" ' + '--logs-dir "/tmp/letsencrypt-log" ' +
'--cert-path "/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem" ' + `--cert-path '/etc/letsencrypt/live/npm-${certificate.id}/fullchain.pem' ` +
'--delete-after-revoke ' + '--delete-after-revoke ' +
(letsencryptStaging ? '--staging' : ''); (letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') +
(letsencryptStaging && letsencryptServer === null ? '--staging ' : '');
// Don't fail command if file does not exist // Don't fail command if file does not exist
const delete_credentialsCmd = `rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`; const delete_credentialsCmd = `rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`;

View File

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

View File

@ -45,25 +45,6 @@ const configure = () => {
}; };
return; return;
} }
const envPostgresqlHost = process.env.DB_POSTGRESQL_HOST || null;
const envPostgresqlUser = process.env.DB_POSTGRESQL_USER || null;
const envPostgresqlName = process.env.DB_POSTGRESQL_NAME || null;
if (envPostgresqlHost && envPostgresqlUser && envPostgresqlName) {
// we have enough mysql creds to go with mysql
logger.info('Using POSTGRESQL configuration');
instance = {
database: {
engine: 'pg',
host: envPostgresqlHost,
port: process.env.DB_POSTGRESQL_PORT || 3306,
user: envPostgresqlUser,
password: process.env.DB_POSTGRESQL_PASSWORD,
name: envPostgresqlName,
},
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}`);
@ -199,5 +180,15 @@ 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

@ -23,7 +23,6 @@
"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.0",
"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"

View File

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

View File

@ -23,9 +23,7 @@
"locations", "locations",
"hsts_enabled", "hsts_enabled",
"hsts_subdomains", "hsts_subdomains",
"certificate", "certificate"
"use_default_location",
"ipv6"
], ],
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
@ -151,12 +149,6 @@
"$ref": "./access-list-object.json" "$ref": "./access-list-object.json"
} }
] ]
},
"use_default_location": {
"type": "boolean"
},
"ipv6": {
"type": "boolean"
} }
} }
} }

View File

@ -28,7 +28,7 @@
}, },
"forward_scheme": { "forward_scheme": {
"type": "string", "type": "string",
"enum": ["http", "https"] "enum": ["auto", "http", "https"]
}, },
"forward_domain_name": { "forward_domain_name": {
"description": "Domain Name", "description": "Domain Name",

View File

@ -25,7 +25,7 @@
"value": { "value": {
"description": "Value in almost any form", "description": "Value in almost any form",
"example": "congratulations", "example": "congratulations",
"oneOf": [ "anyOf": [
{ {
"type": "string", "type": "string",
"minLength": 1 "minLength": 1
@ -46,7 +46,10 @@
}, },
"meta": { "meta": {
"description": "Extra metadata", "description": "Extra metadata",
"example": {}, "example": {
"redirect": "http://example.com",
"html": "<h1>404</h1>"
},
"type": "object" "type": "object"
} }
} }

View File

@ -55,6 +55,25 @@
"certificate_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC1n9j9C5Bes1nd\nqACDckERauxXVNKCnUlUM1buGBx1xc+j2e2Ar23wUJJuWBY18VfT8yqfqVDktO2w\nrbmvZvLuPmXePOKbIKS+XXh+2NG9L5bDG9rwGFCRXnbQj+GWCdMfzx14+CR1IHge\nYz6Cv/Si2/LJPCh/CoBfM4hUQJON3lxAWrWBpdbZnKYMrxuPBRfW9OuzTbCVXToQ\noxRAHiOR9081Xn1WeoKr7kVBIa5UphlvWXa12w1YmUwJu7YndnJGIavLWeNCVc7Z\nEo+nS8Wr/4QWicatIWZXpVaEOPhRoeplQDxNWg5b/Q26rYoVd7PrCmRs7sVcH79X\nzGONeH1PAgMBAAECggEAANb3Wtwl07pCjRrMvc7WbC0xYIn82yu8/g2qtjkYUJcU\nia5lQbYN7RGCS85Oc/tkq48xQEG5JQWNH8b918jDEMTrFab0aUEyYcru1q9L8PL6\nYHaNgZSrMrDcHcS8h0QOXNRJT5jeGkiHJaTR0irvB526tqF3knbK9yW22KTfycUe\na0Z9voKn5xRk1DCbHi/nk2EpT7xnjeQeLFaTIRXbS68omkr4YGhwWm5OizoyEGZu\nW0Zum5BkQyMr6kor3wdxOTG97ske2rcyvvHi+ErnwL0xBv0qY0Dhe8DpuXpDezqw\no72yY8h31Fu84i7sAj24YuE5Df8DozItFXQpkgbQ6QKBgQDPrufhvIFm2S/MzBdW\nH8JxY7CJlJPyxOvc1NIl9RczQGAQR90kx52cgIcuIGEG6/wJ/xnGfMmW40F0DnQ+\nN+oLgB9SFxeLkRb7s9Z/8N3uIN8JJFYcerEOiRQeN2BXEEWJ7bUThNtsVrAcKoUh\nELsDmnHW/3V+GKwhd0vpk842+wKBgQDf4PGLG9PTE5tlAoyHFodJRd2RhTJQkwsU\nMDNjLJ+KecLv+Nl+QiJhoflG1ccqtSFlBSCG067CDQ5LV0xm3mLJ7pfJoMgjcq31\nqjEmX4Ls91GuVOPtbwst3yFKjsHaSoKB5fBvWRcKFpBUezM7Qcw2JP3+dQT+bQIq\ncMTkRWDSvQKBgQDOdCQFDjxg/lR7NQOZ1PaZe61aBz5P3pxNqa7ClvMaOsuEQ7w9\nvMYcdtRq8TsjA2JImbSI0TIg8gb2FQxPcYwTJKl+FICOeIwtaSg5hTtJZpnxX5LO\nutTaC0DZjNkTk5RdOdWA8tihyUdGqKoxJY2TVmwGe2rUEDjFB++J4inkEwKBgB6V\ng0nmtkxanFrzOzFlMXwgEEHF+Xaqb9QFNa/xs6XeNnREAapO7JV75Cr6H2hFMFe1\nmJjyqCgYUoCWX3iaHtLJRnEkBtNY4kzyQB6m46LtsnnnXO/dwKA2oDyoPfFNRoDq\nYatEd3JIXNU9s2T/+x7WdOBjKhh72dTkbPFmTPDdAoGAU6rlPBevqOFdObYxdPq8\nEQWu44xqky3Mf5sBpOwtu6rqCYuziLiN7K4sjN5GD5mb1cEU+oS92ZiNcUQ7MFXk\n8yTYZ7U0VcXyAcpYreWwE8thmb0BohJBr+Mp3wLTx32x0HKdO6vpUa0d35LUTUmM\nRrKmPK/msHKK/sVHiL+NFqo=\n-----END PRIVATE KEY-----\n" "certificate_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC1n9j9C5Bes1nd\nqACDckERauxXVNKCnUlUM1buGBx1xc+j2e2Ar23wUJJuWBY18VfT8yqfqVDktO2w\nrbmvZvLuPmXePOKbIKS+XXh+2NG9L5bDG9rwGFCRXnbQj+GWCdMfzx14+CR1IHge\nYz6Cv/Si2/LJPCh/CoBfM4hUQJON3lxAWrWBpdbZnKYMrxuPBRfW9OuzTbCVXToQ\noxRAHiOR9081Xn1WeoKr7kVBIa5UphlvWXa12w1YmUwJu7YndnJGIavLWeNCVc7Z\nEo+nS8Wr/4QWicatIWZXpVaEOPhRoeplQDxNWg5b/Q26rYoVd7PrCmRs7sVcH79X\nzGONeH1PAgMBAAECggEAANb3Wtwl07pCjRrMvc7WbC0xYIn82yu8/g2qtjkYUJcU\nia5lQbYN7RGCS85Oc/tkq48xQEG5JQWNH8b918jDEMTrFab0aUEyYcru1q9L8PL6\nYHaNgZSrMrDcHcS8h0QOXNRJT5jeGkiHJaTR0irvB526tqF3knbK9yW22KTfycUe\na0Z9voKn5xRk1DCbHi/nk2EpT7xnjeQeLFaTIRXbS68omkr4YGhwWm5OizoyEGZu\nW0Zum5BkQyMr6kor3wdxOTG97ske2rcyvvHi+ErnwL0xBv0qY0Dhe8DpuXpDezqw\no72yY8h31Fu84i7sAj24YuE5Df8DozItFXQpkgbQ6QKBgQDPrufhvIFm2S/MzBdW\nH8JxY7CJlJPyxOvc1NIl9RczQGAQR90kx52cgIcuIGEG6/wJ/xnGfMmW40F0DnQ+\nN+oLgB9SFxeLkRb7s9Z/8N3uIN8JJFYcerEOiRQeN2BXEEWJ7bUThNtsVrAcKoUh\nELsDmnHW/3V+GKwhd0vpk842+wKBgQDf4PGLG9PTE5tlAoyHFodJRd2RhTJQkwsU\nMDNjLJ+KecLv+Nl+QiJhoflG1ccqtSFlBSCG067CDQ5LV0xm3mLJ7pfJoMgjcq31\nqjEmX4Ls91GuVOPtbwst3yFKjsHaSoKB5fBvWRcKFpBUezM7Qcw2JP3+dQT+bQIq\ncMTkRWDSvQKBgQDOdCQFDjxg/lR7NQOZ1PaZe61aBz5P3pxNqa7ClvMaOsuEQ7w9\nvMYcdtRq8TsjA2JImbSI0TIg8gb2FQxPcYwTJKl+FICOeIwtaSg5hTtJZpnxX5LO\nutTaC0DZjNkTk5RdOdWA8tihyUdGqKoxJY2TVmwGe2rUEDjFB++J4inkEwKBgB6V\ng0nmtkxanFrzOzFlMXwgEEHF+Xaqb9QFNa/xs6XeNnREAapO7JV75Cr6H2hFMFe1\nmJjyqCgYUoCWX3iaHtLJRnEkBtNY4kzyQB6m46LtsnnnXO/dwKA2oDyoPfFNRoDq\nYatEd3JIXNU9s2T/+x7WdOBjKhh72dTkbPFmTPDdAoGAU6rlPBevqOFdObYxdPq8\nEQWu44xqky3Mf5sBpOwtu6rqCYuziLiN7K4sjN5GD5mb1cEU+oS92ZiNcUQ7MFXk\n8yTYZ7U0VcXyAcpYreWwE8thmb0BohJBr+Mp3wLTx32x0HKdO6vpUa0d35LUTUmM\nRrKmPK/msHKK/sVHiL+NFqo=\n-----END PRIVATE KEY-----\n"
} }
} }
},
"schema": {
"type": "object",
"additionalProperties": false,
"required": ["certificate", "certificate_key"],
"properties": {
"certificate": {
"type": "string",
"minLength": 1
},
"certificate_key": {
"type": "string",
"minLength": 1
},
"intermediate_certificate": {
"type": "string",
"minLength": 1
}
}
} }
} }
} }

View File

@ -94,9 +94,7 @@
"avatar": "", "avatar": "",
"roles": ["admin"] "roles": ["admin"]
}, },
"certificate": null, "certificate": null
"use_default_location": true,
"ipv6": true
} }
} }
}, },

View File

@ -79,9 +79,7 @@
"nickname": "Admin", "nickname": "Admin",
"avatar": "", "avatar": "",
"roles": ["admin"] "roles": ["admin"]
}, }
"use_default_location": true,
"ipv6": true
} }
} }
}, },

View File

@ -129,9 +129,7 @@
"roles": ["admin"] "roles": ["admin"]
}, },
"certificate": null, "certificate": null,
"access_list": null, "access_list": null
"use_default_location": true,
"ipv6": true
} }
} }
}, },

View File

@ -114,9 +114,7 @@
"avatar": "//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?default=mm", "avatar": "//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?default=mm",
"roles": ["admin"] "roles": ["admin"]
}, },
"access_list": null, "access_list": null
"use_default_location": true,
"ipv6": true
} }
} }
}, },

View File

@ -114,9 +114,7 @@
"avatar": "", "avatar": "",
"roles": ["admin"] "roles": ["admin"]
}, },
"certificate": null, "certificate": null
"use_default_location": true,
"ipv6": true
} }
} }
}, },

View File

@ -99,9 +99,7 @@
"nickname": "Admin", "nickname": "Admin",
"avatar": "", "avatar": "",
"roles": ["admin"] "roles": ["admin"]
}, }
"use_default_location": true,
"ipv6": true
} }
} }
}, },

View File

@ -129,9 +129,7 @@
"roles": ["admin"] "roles": ["admin"]
}, },
"certificate": null, "certificate": null,
"access_list": null, "access_list": null
"use_default_location": true,
"ipv6": true
} }
} }
}, },

View File

@ -13,7 +13,8 @@
"name": "settingID", "name": "settingID",
"schema": { "schema": {
"type": "string", "type": "string",
"minLength": 1 "minLength": 1,
"enum": ["default-site"]
}, },
"required": true, "required": true,
"description": "Setting ID", "description": "Setting ID",
@ -31,10 +32,21 @@
"minProperties": 1, "minProperties": 1,
"properties": { "properties": {
"value": { "value": {
"$ref": "../../../components/setting-object.json#/properties/value" "type": "string",
"minLength": 1,
"enum": ["congratulations", "404", "444", "redirect", "html"]
}, },
"meta": { "meta": {
"$ref": "../../../components/setting-object.json#/properties/meta" "type": "object",
"additionalProperties": false,
"properties": {
"redirect": {
"type": "string"
},
"html": {
"type": "string"
}
}
} }
} }
} }

View File

@ -15,10 +15,11 @@ const certbot = require('./lib/certbot');
const setupDefaultUser = () => { const setupDefaultUser = () => {
return userModel return userModel
.query() .query()
.select('id') .select(userModel.raw('COUNT(`id`) as `count`'))
.where('is_deleted', 0) .where('is_deleted', 0)
.first()
.then((row) => { .then((row) => {
if (!row.length || !row[0].id) { if (!row.count) {
// Create a new user and set password // Create a new user and set password
let email = process.env.INITIAL_ADMIN_EMAIL || 'admin@example.com'; let email = process.env.INITIAL_ADMIN_EMAIL || 'admin@example.com';
let password = process.env.INITIAL_ADMIN_PASSWORD || 'changeme'; let password = process.env.INITIAL_ADMIN_PASSWORD || 'changeme';
@ -76,10 +77,11 @@ const setupDefaultUser = () => {
const setupDefaultSettings = () => { const setupDefaultSettings = () => {
return settingModel return settingModel
.query() .query()
.select('id') .select(settingModel.raw('COUNT(`id`) as `count`'))
.where({id: 'default-site'}) .where({id: 'default-site'})
.first()
.then((row) => { .then((row) => {
if (!row.length || !row[0].id) { if (!row.count) {
settingModel settingModel
.query() .query()
.insert({ .insert({

View File

@ -2735,67 +2735,11 @@ path@^0.12.7:
process "^0.11.1" process "^0.11.1"
util "^0.10.3" util "^0.10.3"
pg-cloudflare@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz#e6d5833015b170e23ae819e8c5d7eaedb472ca98"
integrity sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==
pg-connection-string@2.5.0: pg-connection-string@2.5.0:
version "2.5.0" version "2.5.0"
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34" resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34"
integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ== integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==
pg-connection-string@^2.7.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.7.0.tgz#f1d3489e427c62ece022dba98d5262efcb168b37"
integrity sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==
pg-int8@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c"
integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==
pg-pool@^3.7.0:
version "3.7.0"
resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.7.0.tgz#d4d3c7ad640f8c6a2245adc369bafde4ebb8cbec"
integrity sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==
pg-protocol@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.7.0.tgz#ec037c87c20515372692edac8b63cf4405448a93"
integrity sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==
pg-types@^2.1.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3"
integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==
dependencies:
pg-int8 "1.0.1"
postgres-array "~2.0.0"
postgres-bytea "~1.0.0"
postgres-date "~1.0.4"
postgres-interval "^1.1.0"
pg@^8.13.0:
version "8.13.0"
resolved "https://registry.yarnpkg.com/pg/-/pg-8.13.0.tgz#e3d245342eb0158112553fcc1890a60720ae2a3d"
integrity sha512-34wkUTh3SxTClfoHB3pQ7bIMvw9dpFU1audQQeZG837fmHfHpr14n/AELVDoOYVDW2h5RDWU78tFjkD+erSBsw==
dependencies:
pg-connection-string "^2.7.0"
pg-pool "^3.7.0"
pg-protocol "^1.7.0"
pg-types "^2.1.0"
pgpass "1.x"
optionalDependencies:
pg-cloudflare "^1.1.1"
pgpass@1.x:
version "1.0.5"
resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d"
integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==
dependencies:
split2 "^4.1.0"
picomatch@^2.0.4, picomatch@^2.2.1: picomatch@^2.0.4, picomatch@^2.2.1:
version "2.2.2" version "2.2.2"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
@ -2814,28 +2758,6 @@ pkg-conf@^2.1.0:
find-up "^2.0.0" find-up "^2.0.0"
load-json-file "^4.0.0" load-json-file "^4.0.0"
postgres-array@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e"
integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==
postgres-bytea@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35"
integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==
postgres-date@~1.0.4:
version "1.0.7"
resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8"
integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==
postgres-interval@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695"
integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==
dependencies:
xtend "^4.0.0"
prelude-ls@^1.2.1: prelude-ls@^1.2.1:
version "1.2.1" version "1.2.1"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
@ -3272,11 +3194,6 @@ socks@^2.6.2:
ip "^2.0.0" ip "^2.0.0"
smart-buffer "^4.2.0" smart-buffer "^4.2.0"
split2@^4.1.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4"
integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==
sprintf-js@~1.0.2: sprintf-js@~1.0.2:
version "1.0.3" version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
@ -3748,11 +3665,6 @@ xdg-basedir@^4.0.0:
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==
xtend@^4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
y18n@^4.0.0: y18n@^4.0.0:
version "4.0.1" version "4.0.1"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4"

View File

@ -3,6 +3,8 @@
# This file assumes that the frontend has been built using ./scripts/frontend-build # This file assumes that the frontend has been built using ./scripts/frontend-build
FROM nginxproxymanager/testca AS testca
FROM letsencrypt/pebble AS pebbleca
FROM nginxproxymanager/nginx-full:certbot-node FROM nginxproxymanager/nginx-full:certbot-node
ARG TARGETPLATFORM ARG TARGETPLATFORM
@ -45,6 +47,8 @@ RUN yarn install \
# add late to limit cache-busting by modifications # add late to limit cache-busting by modifications
COPY docker/rootfs / COPY docker/rootfs /
COPY --from=pebbleca /test/certs/pebble.minica.pem /etc/ssl/certs/pebble.minica.pem
COPY --from=testca /home/step/certs/root_ca.crt /etc/ssl/certs/NginxProxyManager.crt
# Remove frontend service not required for prod, dev nginx config as well # Remove frontend service not required for prod, dev nginx config as well
RUN rm -rf /etc/s6-overlay/s6-rc.d/user/contents.d/frontend /etc/nginx/conf.d/dev.conf \ RUN rm -rf /etc/s6-overlay/s6-rc.d/user/contents.d/frontend /etc/nginx/conf.d/dev.conf \

View File

@ -1,7 +1,10 @@
FROM nginxproxymanager/testca AS testca
FROM letsencrypt/pebble AS pebbleca
FROM nginxproxymanager/nginx-full:certbot-node FROM nginxproxymanager/nginx-full:certbot-node
LABEL maintainer="Jamie Curnow <jc@jc21.com>" LABEL maintainer="Jamie Curnow <jc@jc21.com>"
# See: https://github.com/just-containers/s6-overlay/blob/master/README.md SHELL ["/bin/bash", "-o", "pipefail", "-c"]
ENV SUPPRESS_NO_CONFIG_WARNING=1 \ ENV SUPPRESS_NO_CONFIG_WARNING=1 \
S6_BEHAVIOUR_IF_STAGE2_FAILS=1 \ S6_BEHAVIOUR_IF_STAGE2_FAILS=1 \
S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0 \ S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0 \
@ -17,18 +20,20 @@ RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Task # Task
RUN cd /usr \ WORKDIR /usr
&& curl -sL https://taskfile.dev/install.sh | sh \ RUN curl -sL https://taskfile.dev/install.sh | sh
&& cd /root WORKDIR /root
COPY rootfs / COPY rootfs /
RUN rm -f /etc/nginx/conf.d/production.conf
RUN chmod 644 /etc/logrotate.d/nginx-proxy-manager
# s6 overlay
COPY scripts/install-s6 /tmp/install-s6 COPY scripts/install-s6 /tmp/install-s6
RUN /tmp/install-s6 "${TARGETPLATFORM}" && rm -f /tmp/install-s6 RUN rm -f /etc/nginx/conf.d/production.conf \
RUN chmod 644 -R /root/.cache && chmod 644 /etc/logrotate.d/nginx-proxy-manager \
&& /tmp/install-s6 "${TARGETPLATFORM}" \
&& rm -f /tmp/install-s6
# Certs for testing purposes
COPY --from=pebbleca /test/certs/pebble.minica.pem /etc/ssl/certs/pebble.minica.pem
COPY --from=testca /home/step/certs/root_ca.crt /etc/ssl/certs/NginxProxyManager.crt
EXPOSE 80 81 443 EXPOSE 80 81 443
ENTRYPOINT [ "/init" ] ENTRYPOINT [ "/init" ]

View File

@ -1,29 +0,0 @@
# WARNING: This is a CI docker-compose file used for building and testing of the entire app, it should not be used for production.
services:
fullstack:
environment:
DB_POSTGRESQL_HOST: 'db'
DB_POSTGRESQL_PORT: '5432'
DB_POSTGRESQL_USER: 'npm'
DB_POSTGRESQL_PASSWORD: 'npmpass'
DB_POSTGRESQL_NAME: 'npm'
depends_on:
- db-postgresql
db-postgresql:
image: postgres:14.2-alpine
environment:
POSTGRES_PASSWORD: "npmpass"
POSTGRES_USER: "npm"
POSTGRES_DB: "npm"
ports:
- 5432:5432
volumes:
- postgres_vol:/var/lib/postgresql/data
networks:
- fulltest
volumes:
postgres_vol:

View File

@ -9,6 +9,9 @@ services:
environment: environment:
DEBUG: 'true' DEBUG: 'true'
FORCE_COLOR: 1 FORCE_COLOR: 1
# Required for DNS Certificate provisioning in CI
LE_SERVER: 'https://ca.internal/acme/acme/directory'
REQUESTS_CA_BUNDLE: '/etc/ssl/certs/NginxProxyManager.crt'
volumes: volumes:
- 'npm_data_ci:/data' - 'npm_data_ci:/data'
- 'npm_le_ci:/etc/letsencrypt' - 'npm_le_ci:/etc/letsencrypt'

View File

@ -1,7 +1,7 @@
# WARNING: This is a DEVELOPMENT docker-compose file, it should not be used for production. # WARNING: This is a DEVELOPMENT docker-compose file, it should not be used for production.
services: services:
npm: fullstack:
image: nginxproxymanager:dev image: nginxproxymanager:dev
container_name: npm_core container_name: npm_core
build: build:
@ -33,12 +33,20 @@ services:
DB_MYSQL_NAME: 'npm' DB_MYSQL_NAME: 'npm'
# DB_SQLITE_FILE: "/data/database.sqlite" # DB_SQLITE_FILE: "/data/database.sqlite"
# DISABLE_IPV6: "true" # DISABLE_IPV6: "true"
# Required for DNS Certificate provisioning testing:
LE_SERVER: 'https://ca.internal/acme/acme/directory'
REQUESTS_CA_BUNDLE: '/etc/ssl/certs/NginxProxyManager.crt'
volumes: volumes:
- npm_data:/data - npm_data:/data
- le_data:/etc/letsencrypt - le_data:/etc/letsencrypt
- './dev/resolv.conf:/etc/resolv.conf:ro'
- ../backend:/app - ../backend:/app
- ../frontend:/app/frontend - ../frontend:/app/frontend
- ../global:/app/global - ../global:/app/global
healthcheck:
test: ["CMD", "/usr/bin/check-health"]
interval: 10s
timeout: 3s
depends_on: depends_on:
- db - db
working_dir: /app working_dir: /app
@ -58,6 +66,23 @@ services:
volumes: volumes:
- db_data:/var/lib/mysql - db_data:/var/lib/mysql
stepca:
image: jc21/testca
volumes:
- './dev/resolv.conf:/etc/resolv.conf:ro'
- '/etc/localtime:/etc/localtime:ro'
networks:
nginx_proxy_manager:
aliases:
- ca.internal
dnsrouter:
image: jc21/dnsrouter
volumes:
- ./dev/dnsrouter-config.json.tmp:/dnsrouter-config.json:ro
networks:
- nginx_proxy_manager
swagger: swagger:
image: swaggerapi/swagger-ui:latest image: swaggerapi/swagger-ui:latest
container_name: npm_swagger container_name: npm_swagger
@ -67,19 +92,78 @@ services:
URL: "http://npm:81/api/schema" URL: "http://npm:81/api/schema"
PORT: '80' PORT: '80'
depends_on: depends_on:
- npm - fullstack
squid: squid:
image: ubuntu/squid image: ubuntu/squid
container_name: npm_squid container_name: npm_squid
volumes: volumes:
- './dev/squid.conf:/etc/squid/squid.conf:ro' - './dev/squid.conf:/etc/squid/squid.conf:ro'
- './dev/resolv.conf:/etc/resolv.conf:ro'
- '/etc/localtime:/etc/localtime:ro' - '/etc/localtime:/etc/localtime:ro'
networks: networks:
- nginx_proxy_manager - nginx_proxy_manager
ports: ports:
- 8128:3128 - 8128:3128
pdns:
image: pschiffe/pdns-mysql
volumes:
- '/etc/localtime:/etc/localtime:ro'
environment:
PDNS_master: 'yes'
PDNS_api: 'yes'
PDNS_api_key: 'npm'
PDNS_webserver: 'yes'
PDNS_webserver_address: '0.0.0.0'
PDNS_webserver_password: 'npm'
PDNS_webserver-allow-from: '127.0.0.0/8,192.0.0.0/8,10.0.0.0/8,172.0.0.0/8'
PDNS_version_string: 'anonymous'
PDNS_default_ttl: 1500
PDNS_allow_axfr_ips: '127.0.0.0/8,192.0.0.0/8,10.0.0.0/8,172.0.0.0/8'
PDNS_gmysql_host: pdns-db
PDNS_gmysql_port: 3306
PDNS_gmysql_user: pdns
PDNS_gmysql_password: pdns
PDNS_gmysql_dbname: pdns
depends_on:
- pdns-db
networks:
nginx_proxy_manager:
aliases:
- ns1.pdns
- ns2.pdns
pdns-db:
image: mariadb
environment:
MYSQL_ROOT_PASSWORD: 'pdns'
MYSQL_DATABASE: 'pdns'
MYSQL_USER: 'pdns'
MYSQL_PASSWORD: 'pdns'
volumes:
- 'pdns_mysql:/var/lib/mysql'
- '/etc/localtime:/etc/localtime:ro'
- './dev/pdns-db.sql:/docker-entrypoint-initdb.d/01_init.sql:ro'
networks:
- nginx_proxy_manager
cypress:
image: "npm_dev_cypress"
build:
context: ../
dockerfile: test/cypress/Dockerfile
environment:
HTTP_PROXY: 'squid:3128'
HTTPS_PROXY: 'squid:3128'
volumes:
- '../test/results:/results'
- './dev/resolv.conf:/etc/resolv.conf:ro'
- '/etc/localtime:/etc/localtime:ro'
command: cypress run --browser chrome --config-file=cypress/config/ci.js
networks:
- nginx_proxy_manager
volumes: volumes:
npm_data: npm_data:
name: npm_core_data name: npm_core_data
@ -87,6 +171,8 @@ volumes:
name: npm_le_data name: npm_le_data
db_data: db_data:
name: npm_db_data name: npm_db_data
pdns_mysql:
name: npm_pdns_mysql
networks: networks:
nginx_proxy_manager: nginx_proxy_manager:

View File

@ -1,91 +0,0 @@
# WARNING: This is a DEVELOPMENT docker-compose file, it should not be used for production.
services:
npm:
image: nginxproxymanager:dev
container_name: npm_core
build:
context: ./
dockerfile: ./dev/Dockerfile
ports:
- 3080:80
- 3081:81
- 3443:443
networks:
- nginx_proxy_manager
environment:
PUID: 1000
PGID: 1000
FORCE_COLOR: 1
# specifically for dev:
DEBUG: 'true'
DEVELOPMENT: 'true'
LE_STAGING: 'true'
# db:
DB_POSTGRESQL_HOST: 'db'
DB_POSTGRESQL_PORT: '5432'
DB_POSTGRESQL_USER: 'npm'
DB_POSTGRESQL_PASSWORD: 'npmpass'
DB_POSTGRESQL_NAME: 'npm'
# DB_SQLITE_FILE: "/data/database.sqlite"
# DISABLE_IPV6: "true"
volumes:
- npm_data:/data
- le_data:/etc/letsencrypt
- ../backend:/app
- ../frontend:/app/frontend
- ../global:/app/global
depends_on:
- db
working_dir: /app
db:
image: postgres:14.2-alpine
container_name: npm_db
ports:
- 5432:5432
networks:
- nginx_proxy_manager
environment:
POSTGRES_PASSWORD: "npmpass"
POSTGRES_USER: "npm"
POSTGRES_DB: "npm"
volumes:
- db_data:/var/lib/postgresql/data
pgadmin:
image: dpage/pgadmin4
environment:
PGADMIN_DEFAULT_EMAIL: "admin@example.com"
PGADMIN_DEFAULT_PASSWORD: "changeme"
ports:
- 5080:80
networks:
- nginx_proxy_manager
depends_on:
- db
swagger:
image: swaggerapi/swagger-ui:latest
container_name: npm_swagger
ports:
- 3082:80
environment:
URL: "http://npm:81/api/schema"
PORT: '80'
depends_on:
- npm
volumes:
npm_data:
name: npm_core_data
le_data:
name: npm_le_data
db_data:
name: npm_db_data
db_data1:
name: npm_db_data1
networks:
nginx_proxy_manager:
name: npm_network

View File

@ -194,7 +194,7 @@
"freedns": { "freedns": {
"name": "FreeDNS", "name": "FreeDNS",
"package_name": "certbot-dns-freedns", "package_name": "certbot-dns-freedns",
"version": "~=0.1.0", "version": "~=0.2.0",
"dependencies": "", "dependencies": "",
"credentials": "dns_freedns_username = myremoteuser\ndns_freedns_password = verysecureremoteuserpassword", "credentials": "dns_freedns_username = myremoteuser\ndns_freedns_password = verysecureremoteuserpassword",
"full_plugin_name": "dns-freedns" "full_plugin_name": "dns-freedns"

View File

@ -15,3 +15,13 @@ COMPOSE_PROJECT_NAME="npmdev"
COMPOSE_FILE="docker/docker-compose.dev.yml" COMPOSE_FILE="docker/docker-compose.dev.yml"
export COMPOSE_FILE COMPOSE_PROJECT_NAME export COMPOSE_FILE COMPOSE_PROJECT_NAME
# $1: container_name
get_container_ip () {
local container_name=$1
local container
local ip
container=$(docker-compose ps --all -q "${container_name}" | tail -n1)
ip=$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$container")
echo "$ip"
}

13
scripts/cypress-dev Executable file
View File

@ -0,0 +1,13 @@
#!/bin/bash -e
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
. "$DIR/.common.sh"
# Ensure docker-compose exists
if hash docker-compose 2>/dev/null; then
cd "${DIR}/.."
rm -rf "$DIR/../test/results"
docker-compose up --build cypress
else
echo -e "${RED} docker-compose command is not available${RESET}"
fi

View File

@ -7,8 +7,43 @@ DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if hash docker-compose 2>/dev/null; then if hash docker-compose 2>/dev/null; then
cd "${DIR}/.." cd "${DIR}/.."
echo -e "${BLUE} ${CYAN}Starting Dev Stack ...${RESET}" echo -e "${BLUE} ${CYAN}Starting Dev Stack ...${RESET}"
echo -e "${BLUE} $(docker-compose config)${RESET}"
docker-compose up -d --remove-orphans --force-recreate --build # Bring up a stack, in steps so we can inject IPs everywhere
docker-compose up -d pdns pdns-db
PDNS_IP=$(get_container_ip "pdns")
echo -e "${BLUE} ${YELLOW}PDNS IP is ${PDNS_IP}${RESET}"
# adjust the dnsrouter config
LOCAL_DNSROUTER_CONFIG="$DIR/../docker/dev/dnsrouter-config.json"
rm -rf "$LOCAL_DNSROUTER_CONFIG.tmp"
# IMPORTANT: changes to dnsrouter-config.json will affect this line:
jq --arg a "$PDNS_IP" '.servers[0].upstreams[1].upstream = $a' "$LOCAL_DNSROUTER_CONFIG" > "$LOCAL_DNSROUTER_CONFIG.tmp"
docker-compose up -d dnsrouter
DNSROUTER_IP=$(get_container_ip "dnsrouter")
echo -e "${BLUE} ${YELLOW}DNS Router IP is ${DNSROUTER_IP}"
if [ "${DNSROUTER_IP:-}" = "" ]; then
echo -e "${RED} ERROR: DNS Router IP is not set${RESET}"
exit 1
fi
# mount the resolver
LOCAL_RESOLVE="$DIR/../docker/dev/resolv.conf"
rm -rf "${LOCAL_RESOLVE}"
printf "nameserver %s\noptions ndots:0" "${DNSROUTER_IP}" > "${LOCAL_RESOLVE}"
# bring up all remaining containers, except cypress!
docker-compose up -d --remove-orphans stepca squid
docker-compose pull db
docker-compose up -d --remove-orphans --pull=never fullstack
docker-compose up -d --remove-orphans swagger
# docker-compose up -d --remove-orphans --force-recreate --build
# wait for main container to be healthy
bash "$DIR/wait-healthy" "$(docker-compose ps --all -q fullstack)" 120
echo "" echo ""
echo -e "${CYAN}Admin UI: http://127.0.0.1:3081${RESET}" echo -e "${CYAN}Admin UI: http://127.0.0.1:3081${RESET}"

View File

@ -15,7 +15,7 @@ module.exports = defineConfig({
return require("../plugins/index.js")(on, config); return require("../plugins/index.js")(on, config);
}, },
env: { env: {
swaggerBase: 'http://fullstack:81/api/schema', swaggerBase: '{{baseUrl}}/api/schema?ts=' + Date.now(),
}, },
baseUrl: 'http://fullstack:81', baseUrl: 'http://fullstack:81',
} }

View File

@ -1,22 +0,0 @@
const { defineConfig } = require('cypress');
module.exports = defineConfig({
requestTimeout: 30000,
defaultCommandTimeout: 20000,
reporter: 'cypress-multi-reporters',
reporterOptions: {
configFile: 'multi-reporter.json'
},
video: false,
videosFolder: 'results/videos',
screenshotsFolder: 'results/screenshots',
e2e: {
setupNodeEvents(on, config) {
return require("../plugins/index.js")(on, config);
},
env: {
swaggerBase: 'http://npm:81/api/schema',
},
baseUrl: 'http://npm:81',
}
});

View File

@ -1,7 +1,8 @@
/// <reference types="Cypress" /> /// <reference types="cypress" />
describe('Certificates endpoints', () => { describe('Certificates endpoints', () => {
let token; let token;
let certID;
before(() => { before(() => {
cy.getToken().then((tok) => { cy.getToken().then((tok) => {
@ -24,6 +25,54 @@ describe('Certificates endpoints', () => {
}); });
}); });
it('Custom certificate lifecycle', function() {
// Create custom cert
cy.task('backendApiPost', {
token: token,
path: '/api/nginx/certificates',
data: {
provider: "other",
nice_name: "Test Certificate",
},
}).then((data) => {
cy.validateSwaggerSchema('post', 201, '/nginx/certificates', data);
expect(data).to.have.property('id');
certID = data.id;
// Upload files
cy.task('backendApiPostFiles', {
token: token,
path: `/api/nginx/certificates/${certID}/upload`,
files: {
certificate: 'test.example.com.pem',
certificate_key: 'test.example.com-key.pem',
},
}).then((data) => {
cy.validateSwaggerSchema('post', 200, '/nginx/certificates/{certID}/upload', data);
expect(data).to.have.property('certificate');
expect(data).to.have.property('certificate_key');
// Get all certs
cy.task('backendApiGet', {
token: token,
path: '/api/nginx/certificates?expand=owner'
}).then((data) => {
cy.validateSwaggerSchema('get', 200, '/nginx/certificates', data);
expect(data.length).to.be.greaterThan(0);
// Delete cert
cy.task('backendApiDelete', {
token: token,
path: `/api/nginx/certificates/${certID}`
}).then((data) => {
cy.validateSwaggerSchema('delete', 200, '/nginx/certificates/{certID}', data);
expect(data).to.be.equal(true);
});
});
});
});
});
it('Request Certificate - CVE-2024-46256/CVE-2024-46257', function() { it('Request Certificate - CVE-2024-46256/CVE-2024-46257', function() {
cy.task('backendApiPost', { cy.task('backendApiPost', {
token: token, token: token,

View File

@ -0,0 +1,62 @@
/// <reference types="cypress" />
describe('Full Certificate Provisions', () => {
let token;
before(() => {
cy.getToken().then((tok) => {
token = tok;
});
});
it('Should be able to create new http certificate', function() {
cy.task('backendApiPost', {
token: token,
path: '/api/nginx/certificates',
data: {
domain_names: [
'website1.example.com'
],
meta: {
letsencrypt_email: 'admin@example.com',
letsencrypt_agree: true,
dns_challenge: false
},
provider: 'letsencrypt'
}
}).then((data) => {
cy.validateSwaggerSchema('post', 201, '/nginx/certificates', data);
expect(data).to.have.property('id');
expect(data.id).to.be.greaterThan(0);
expect(data.provider).to.be.equal('letsencrypt');
});
});
it('Should be able to create new DNS certificate with Powerdns', function() {
cy.task('backendApiPost', {
token: token,
path: '/api/nginx/certificates',
data: {
domain_names: [
'website2.example.com'
],
meta: {
letsencrypt_email: "admin@example.com",
dns_challenge: true,
dns_provider: 'powerdns',
dns_provider_credentials: 'dns_powerdns_api_url = http://ns1.pdns:8081\r\ndns_powerdns_api_key = npm',
letsencrypt_agree: true,
propagation_seconds: 5,
},
provider: 'letsencrypt'
}
}).then((data) => {
cy.validateSwaggerSchema('post', 201, '/nginx/certificates', data);
expect(data).to.have.property('id');
expect(data.id).to.be.greaterThan(0);
expect(data.provider).to.be.equal('letsencrypt');
expect(data.meta.dns_provider).to.be.equal('powerdns');
});
});
});

View File

@ -1,4 +1,4 @@
/// <reference types="Cypress" /> /// <reference types="cypress" />
describe('Basic API checks', () => { describe('Basic API checks', () => {
it('Should return a valid health payload', function () { it('Should return a valid health payload', function () {
@ -12,7 +12,7 @@ describe('Basic API checks', () => {
it('Should return a valid schema payload', function () { it('Should return a valid schema payload', function () {
cy.task('backendApiGet', { cy.task('backendApiGet', {
path: '/api/schema', path: '/api/schema?ts=' + Date.now(),
}).then((data) => { }).then((data) => {
expect(data.openapi).to.be.equal('3.1.0'); expect(data.openapi).to.be.equal('3.1.0');
}); });

View File

@ -1,6 +1,6 @@
/// <reference types="Cypress" /> /// <reference types="cypress" />
describe('Hosts endpoints', () => { describe('Proxy Hosts endpoints', () => {
let token; let token;
before(() => { before(() => {

View File

@ -0,0 +1,124 @@
/// <reference types="cypress" />
describe('Settings endpoints', () => {
let token;
before(() => {
cy.getToken().then((tok) => {
token = tok;
});
});
it('Get all settings', function() {
cy.task('backendApiGet', {
token: token,
path: '/api/settings',
}).then((data) => {
cy.validateSwaggerSchema('get', 200, '/settings', data);
expect(data.length).to.be.greaterThan(0);
});
});
it('Get default-site setting', function() {
cy.task('backendApiGet', {
token: token,
path: '/api/settings/default-site',
}).then((data) => {
cy.validateSwaggerSchema('get', 200, '/settings/{settingID}', data);
expect(data).to.have.property('id');
expect(data.id).to.be.equal('default-site');
});
});
it('Default Site congratulations', function() {
cy.task('backendApiPut', {
token: token,
path: '/api/settings/default-site',
data: {
value: 'congratulations',
},
}).then((data) => {
cy.validateSwaggerSchema('put', 200, '/settings/{settingID}', data);
expect(data).to.have.property('id');
expect(data.id).to.be.equal('default-site');
expect(data).to.have.property('value');
expect(data.value).to.be.equal('congratulations');
});
});
it('Default Site 404', function() {
cy.task('backendApiPut', {
token: token,
path: '/api/settings/default-site',
data: {
value: '404',
},
}).then((data) => {
cy.validateSwaggerSchema('put', 200, '/settings/{settingID}', data);
expect(data).to.have.property('id');
expect(data.id).to.be.equal('default-site');
expect(data).to.have.property('value');
expect(data.value).to.be.equal('404');
});
});
it('Default Site 444', function() {
cy.task('backendApiPut', {
token: token,
path: '/api/settings/default-site',
data: {
value: '444',
},
}).then((data) => {
cy.validateSwaggerSchema('put', 200, '/settings/{settingID}', data);
expect(data).to.have.property('id');
expect(data.id).to.be.equal('default-site');
expect(data).to.have.property('value');
expect(data.value).to.be.equal('444');
});
});
it('Default Site redirect', function() {
cy.task('backendApiPut', {
token: token,
path: '/api/settings/default-site',
data: {
value: 'redirect',
meta: {
redirect: 'https://www.google.com',
},
},
}).then((data) => {
cy.validateSwaggerSchema('put', 200, '/settings/{settingID}', data);
expect(data).to.have.property('id');
expect(data.id).to.be.equal('default-site');
expect(data).to.have.property('value');
expect(data.value).to.be.equal('redirect');
expect(data).to.have.property('meta');
expect(data.meta).to.have.property('redirect');
expect(data.meta.redirect).to.be.equal('https://www.google.com');
});
});
it('Default Site html', function() {
cy.task('backendApiPut', {
token: token,
path: '/api/settings/default-site',
data: {
value: 'html',
meta: {
html: '<p>hello world</p>'
},
},
}).then((data) => {
cy.validateSwaggerSchema('put', 200, '/settings/{settingID}', data);
expect(data).to.have.property('id');
expect(data.id).to.be.equal('default-site');
expect(data).to.have.property('value');
expect(data.value).to.be.equal('html');
expect(data).to.have.property('meta');
expect(data.meta).to.have.property('html');
expect(data.meta.html).to.be.equal('<p>hello world</p>');
});
});
});

View File

@ -1,4 +1,4 @@
/// <reference types="Cypress" /> /// <reference types="cypress" />
describe('Users endpoints', () => { describe('Users endpoints', () => {
let token; let token;

View File

@ -7,7 +7,7 @@ const BackendApi = function(config, token) {
this.axios = axios.create({ this.axios = axios.create({
baseURL: config.baseUrl, baseURL: config.baseUrl,
timeout: 5000, timeout: 90000,
}); });
}; };
@ -80,7 +80,7 @@ BackendApi.prototype._handleError = function(err, resolve, reject, returnOnError
* @returns {Promise<object>} * @returns {Promise<object>}
*/ */
BackendApi.prototype.request = function (method, path, returnOnError, data) { BackendApi.prototype.request = function (method, path, returnOnError, data) {
logger(method.toUpperCase(), this.config.baseUrl + path); logger(method.toUpperCase(), path);
const options = this._prepareOptions(returnOnError); const options = this._prepareOptions(returnOnError);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

View File

@ -1,4 +1,4 @@
const {SwaggerValidation} = require('@jc21/cypress-swagger-validation'); const { SwaggerValidation } = require('@jc21/cypress-swagger-validation');
module.exports = (on, config) => { module.exports = (on, config) => {
// Replace swaggerBase config var wildcard // Replace swaggerBase config var wildcard

View File

@ -4,7 +4,7 @@
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {
"@jc21/cypress-swagger-validation": "^0.2.7", "@jc21/cypress-swagger-validation": "^0.3.1",
"axios": "^1.7.7", "axios": "^1.7.7",
"cypress": "^13.15.0", "cypress": "^13.15.0",
"cypress-multi-reporters": "^1.6.4", "cypress-multi-reporters": "^1.6.4",
@ -19,8 +19,8 @@
"mocha-junit-reporter": "^2.2.1" "mocha-junit-reporter": "^2.2.1"
}, },
"scripts": { "scripts": {
"cypress": "HTTP_PROXY=127.0.0.1:8128 HTTPS_PROXY=127.0.0.1:8128 cypress open --config-file=cypress/config/dev.js", "cypress": "HTTP_PROXY=127.0.0.1:8128 HTTPS_PROXY=127.0.0.1:8128 cypress open --config-file=cypress/config/ci.js",
"cypress:headless": "HTTP_PROXY=127.0.0.1:8128 HTTPS_PROXY=127.0.0.1:8128 cypress run --config-file=cypress/config/dev.js" "cypress:headless": "HTTP_PROXY=127.0.0.1:8128 HTTPS_PROXY=127.0.0.1:8128 cypress run --config-file=cypress/config/ci.js"
}, },
"author": "", "author": "",
"license": "ISC" "license": "ISC"

View File

@ -11,14 +11,13 @@
call-me-maybe "^1.0.1" call-me-maybe "^1.0.1"
js-yaml "^3.13.1" js-yaml "^3.13.1"
"@apidevtools/json-schema-ref-parser@9.0.9": "@apidevtools/json-schema-ref-parser@^11.7.2":
version "9.0.9" version "11.7.2"
resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz#d720f9256e3609621280584f2b47ae165359268b" resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.2.tgz#cdf3e0aded21492364a70e193b45b7cf4177f031"
integrity sha512-GBD2Le9w2+lVFoc4vswGI/TjkNIZSVp7+9xPf+X3uidBfWnAeUWmquteSyt0+VCrhNMWj/FTABISQrD3Z/YA+w== integrity sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==
dependencies: dependencies:
"@jsdevtools/ono" "^7.1.3" "@jsdevtools/ono" "^7.1.3"
"@types/json-schema" "^7.0.6" "@types/json-schema" "^7.0.15"
call-me-maybe "^1.0.1"
js-yaml "^4.1.0" js-yaml "^4.1.0"
"@apidevtools/openapi-schemas@^2.1.0": "@apidevtools/openapi-schemas@^2.1.0":
@ -167,15 +166,16 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.1.tgz#c72a5c76a9fbaf3488e231b13dc52c0da7bab42a" resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.1.tgz#c72a5c76a9fbaf3488e231b13dc52c0da7bab42a"
integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA== integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==
"@jc21/cypress-swagger-validation@^0.2.7": "@jc21/cypress-swagger-validation@^0.3.1":
version "0.2.7" version "0.3.1"
resolved "https://registry.yarnpkg.com/@jc21/cypress-swagger-validation/-/cypress-swagger-validation-0.2.7.tgz#64642b12d98b884df8c30b72852162941285d2af" resolved "https://registry.yarnpkg.com/@jc21/cypress-swagger-validation/-/cypress-swagger-validation-0.3.1.tgz#1cdd49850a20f876ed62149623f99988264751be"
integrity sha512-4EQ0gfigRwVVl3DnVYbR48/EKGnn7oH5YYdMzf6zqypO+bqYvDHu9kgk/WqkGlT/aauGQ7e0YGMo8ZvR7mL0Ng== integrity sha512-Vdt1gLfj8p0tJhA42Cfn43XBbsKocNfVCEVSwkn7RmZgWUyRKjqhBBRTVa9cKZTozyg8Co/yhBMsNyjmHFVXtQ==
dependencies: dependencies:
"@apidevtools/json-schema-ref-parser" "^11.7.2"
"@apidevtools/swagger-parser" "^10.1.0" "@apidevtools/swagger-parser" "^10.1.0"
ajv "^8.17.1" ajv "^8.17.1"
axios "^1.7.7"
json-schema "^0.4.0" json-schema "^0.4.0"
json-schema-ref-parser "^9.0.9"
jsonpath "^1.1.1" jsonpath "^1.1.1"
lodash "^4.17.21" lodash "^4.17.21"
openapi-types "^12.1.3" openapi-types "^12.1.3"
@ -196,7 +196,7 @@
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50"
integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==
"@types/json-schema@^7.0.15", "@types/json-schema@^7.0.6": "@types/json-schema@^7.0.15":
version "7.0.15" version "7.0.15"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
@ -1468,13 +1468,6 @@ json-buffer@3.0.1:
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
json-schema-ref-parser@^9.0.9:
version "9.0.9"
resolved "https://registry.yarnpkg.com/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz#66ea538e7450b12af342fa3d5b8458bc1e1e013f"
integrity sha512-qcP2lmGy+JUoQJ4DOQeLaZDqH9qSkeGCK3suKWxJXS82dg728Mn3j97azDMaOUmJAN4uCq91LdPx4K7E8F1a7Q==
dependencies:
"@apidevtools/json-schema-ref-parser" "9.0.9"
json-schema-traverse@^0.4.1: json-schema-traverse@^0.4.1:
version "0.4.1" version "0.4.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"