Compare commits
153 Commits
Author | SHA1 | Date | |
---|---|---|---|
301499dc52 | |||
5c2f13ed8e | |||
e30ad81f69 | |||
21f36f535f | |||
c14236823a | |||
551a9fe1c6 | |||
e3399e1035 | |||
c413b4af3f | |||
dbf5dec23b | |||
10f0eb17d7 | |||
e3b680c351 | |||
0df0545777 | |||
165bfc9f5f | |||
5830bd73b9 | |||
3c4ce839b9 | |||
ac9f052309 | |||
049e424957 | |||
07e78aec48 | |||
3fec135fe5 | |||
867fe1322b | |||
95208a50a7 | |||
514b13fcc2 | |||
4cbc1f5bbe | |||
64de36cdf2 | |||
093b48ad7b | |||
05f6a55a0b | |||
2523424f68 | |||
b81325d7bf | |||
3e10b7b2b1 | |||
e5cb750015 | |||
28f72086ec | |||
a6b9bd7b01 | |||
2c5eac9dad | |||
87f61b8527 | |||
74bfe490c6 | |||
015167f34d | |||
4bafc7ff1a | |||
bf8beb50b4 | |||
e5034a34f5 | |||
a561605653 | |||
e8596c1554 | |||
ab67481e99 | |||
1b611e67c8 | |||
c5aa2b9f77 | |||
cff6c4d1f5 | |||
077cf75ef2 | |||
ff1770204c | |||
b9a95840e0 | |||
2d7576c57e | |||
251aac716a | |||
6694a42270 | |||
f78a4c6ad1 | |||
83fad8bcda | |||
f539e813aa | |||
5d65166777 | |||
70346138a7 | |||
d68656559c | |||
01660b5b80 | |||
74010acd85 | |||
7c7d255172 | |||
058f1e9835 | |||
b4fc629ec0 | |||
ae06b2da75 | |||
54d423a11f | |||
5da6c97a00 | |||
bf2f13443f | |||
9ce4c3fe2f | |||
4a07bf666d | |||
5be46b4b20 | |||
7fd825b76b | |||
b23d59dec7 | |||
492d450d26 | |||
04412f3624 | |||
c41057b28a | |||
8312bc0100 | |||
85ac43bc5e | |||
d1a0780c7a | |||
f9b8d76527 | |||
26f00eeae4 | |||
1bc2df2178 | |||
8dfbcef198 | |||
6690b7735d | |||
a9e7222e5e | |||
f8edeb2775 | |||
d1786fe159 | |||
157a12fb7c | |||
3f723b1638 | |||
e2e9835d01 | |||
7599617b67 | |||
18a5b11033 | |||
fff31b0f34 | |||
c02e30663a | |||
4c6527cafc | |||
55bddb12e5 | |||
d95861e1fb | |||
94754a5cb3 | |||
546f862236 | |||
f105e29e56 | |||
5c15993d06 | |||
a369ea1080 | |||
98068c0f57 | |||
e0ef8683a2 | |||
66412a75f9 | |||
84d8fb0899 | |||
c631537dbe | |||
8d2f49541c | |||
55a28e3437 | |||
67ea2d01c8 | |||
dab229e37c | |||
7084473330 | |||
dd2e335fae | |||
1ff87bbc12 | |||
2ebfdcf0c9 | |||
8ab161a3ee | |||
e74b9617be | |||
c3d88c83e3 | |||
3e912a7474 | |||
0d726a1d83 | |||
affabf065e | |||
e6ea77d263 | |||
df73c2a458 | |||
96c5c79aef | |||
64922f07ff | |||
bae21f3210 | |||
0702a4e58e | |||
31f1d304d6 | |||
291a74c295 | |||
c0e9d1eb2f | |||
a7cabdde3a | |||
3af560c2d0 | |||
1d23d5c761 | |||
995db12f22 | |||
4c60bfb66b | |||
1716747047 | |||
090b4d0388 | |||
a9f068daa8 | |||
f5ee91aeb3 | |||
e2ee2cbf2d | |||
dcf8364899 | |||
b783602786 | |||
005e64eb9f | |||
e9e5d293cc | |||
a57255350f | |||
781442bf1e | |||
604bd2c576 | |||
d9e1e1bbb7 | |||
907e9e182d | |||
0f238a5021 | |||
8d432bd60a | |||
fd932c7678 | |||
46a9f5cb96 | |||
f990d3f674 | |||
4a6de8deee |
16
.github/ISSUE_TEMPLATE/product_support.md
vendored
Normal file
16
.github/ISSUE_TEMPLATE/product_support.md
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
name: Product Support
|
||||
about: Need help configuring the software?
|
||||
title: ''
|
||||
labels: product-support
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Checklist**
|
||||
- Please read the [setup instructions](https://nginxproxymanager.com/setup/)
|
||||
- Please read the [FAQ](https://nginxproxymanager.com/faq/)
|
||||
|
||||
**What is troubling you?**
|
||||
|
||||
_Clear and concise description of what you're trying to do and what isn't working for you_
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,4 +2,4 @@
|
||||
.idea
|
||||
._*
|
||||
.vscode
|
||||
|
||||
certbot-help.txt
|
||||
|
11
.jenkins/config-sqlite.json
Normal file
11
.jenkins/config-sqlite.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"database": {
|
||||
"engine": "knex-native",
|
||||
"knex": {
|
||||
"client": "sqlite3",
|
||||
"connection": {
|
||||
"filename": "/data/database.sqlite"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
131
Jenkinsfile
vendored
131
Jenkinsfile
vendored
@ -5,6 +5,7 @@ pipeline {
|
||||
options {
|
||||
buildDiscarder(logRotator(numToKeepStr: '5'))
|
||||
disableConcurrentBuilds()
|
||||
ansiColor('xterm')
|
||||
}
|
||||
environment {
|
||||
IMAGE = "nginx-proxy-manager"
|
||||
@ -55,56 +56,77 @@ pipeline {
|
||||
}
|
||||
stage('Frontend') {
|
||||
steps {
|
||||
ansiColor('xterm') {
|
||||
sh './scripts/frontend-build'
|
||||
}
|
||||
sh './scripts/frontend-build'
|
||||
}
|
||||
}
|
||||
stage('Backend') {
|
||||
steps {
|
||||
ansiColor('xterm') {
|
||||
echo 'Checking Syntax ...'
|
||||
// See: https://github.com/yarnpkg/yarn/issues/3254
|
||||
sh '''docker run --rm \\
|
||||
-v "$(pwd)/backend:/app" \\
|
||||
-w /app \\
|
||||
node:latest \\
|
||||
sh -c "yarn install && yarn eslint . && rm -rf node_modules"
|
||||
'''
|
||||
echo 'Checking Syntax ...'
|
||||
// See: https://github.com/yarnpkg/yarn/issues/3254
|
||||
sh '''docker run --rm \\
|
||||
-v "$(pwd)/backend:/app" \\
|
||||
-v "$(pwd)/global:/app/global" \\
|
||||
-w /app \\
|
||||
node:latest \\
|
||||
sh -c "yarn install && yarn eslint . && rm -rf node_modules"
|
||||
'''
|
||||
|
||||
echo 'Docker Build ...'
|
||||
sh '''docker build --pull --no-cache --squash --compress \\
|
||||
-t "${IMAGE}:ci-${BUILD_NUMBER}" \\
|
||||
-f docker/Dockerfile \\
|
||||
--build-arg TARGETPLATFORM=linux/amd64 \\
|
||||
--build-arg BUILDPLATFORM=linux/amd64 \\
|
||||
--build-arg BUILD_VERSION="${BUILD_VERSION}" \\
|
||||
--build-arg BUILD_COMMIT="${BUILD_COMMIT}" \\
|
||||
--build-arg BUILD_DATE="$(date '+%Y-%m-%d %T %Z')" \\
|
||||
.
|
||||
'''
|
||||
}
|
||||
echo 'Docker Build ...'
|
||||
sh '''docker build --pull --no-cache --squash --compress \\
|
||||
-t "${IMAGE}:ci-${BUILD_NUMBER}" \\
|
||||
-f docker/Dockerfile \\
|
||||
--build-arg TARGETPLATFORM=linux/amd64 \\
|
||||
--build-arg BUILDPLATFORM=linux/amd64 \\
|
||||
--build-arg BUILD_VERSION="${BUILD_VERSION}" \\
|
||||
--build-arg BUILD_COMMIT="${BUILD_COMMIT}" \\
|
||||
--build-arg BUILD_DATE="$(date '+%Y-%m-%d %T %Z')" \\
|
||||
.
|
||||
'''
|
||||
}
|
||||
}
|
||||
stage('Test') {
|
||||
stage('Integration Tests Sqlite') {
|
||||
steps {
|
||||
ansiColor('xterm') {
|
||||
// Bring up a stack
|
||||
sh 'docker-compose up -d fullstack'
|
||||
sh './scripts/wait-healthy $(docker-compose ps -q fullstack) 120'
|
||||
// Bring up a stack
|
||||
sh 'docker-compose up -d fullstack-sqlite'
|
||||
sh './scripts/wait-healthy $(docker-compose ps -q fullstack-sqlite) 120'
|
||||
|
||||
// Run tests
|
||||
sh 'rm -rf test/results'
|
||||
sh 'docker-compose up cypress'
|
||||
// Get results
|
||||
sh 'docker cp -L "$(docker-compose ps -q cypress):/results" test/'
|
||||
}
|
||||
// Run tests
|
||||
sh 'rm -rf test/results'
|
||||
sh 'docker-compose up cypress-sqlite'
|
||||
// Get results
|
||||
sh 'docker cp -L "$(docker-compose ps -q cypress-sqlite):/test/results" test/'
|
||||
}
|
||||
post {
|
||||
always {
|
||||
// Dumps to analyze later
|
||||
sh 'mkdir -p debug'
|
||||
sh 'docker-compose logs fullstack | gzip > debug/docker_fullstack.log.gz'
|
||||
sh 'docker-compose logs fullstack-sqlite | gzip > debug/docker_fullstack_sqlite.log.gz'
|
||||
sh 'docker-compose logs db | gzip > debug/docker_db.log.gz'
|
||||
// Cypress videos and screenshot artifacts
|
||||
dir(path: 'test/results') {
|
||||
archiveArtifacts allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml'
|
||||
}
|
||||
junit 'test/results/junit/*'
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Integration Tests Mysql') {
|
||||
steps {
|
||||
// Bring up a stack
|
||||
sh 'docker-compose up -d fullstack-mysql'
|
||||
sh './scripts/wait-healthy $(docker-compose ps -q fullstack-mysql) 120'
|
||||
|
||||
// Run tests
|
||||
sh 'rm -rf test/results'
|
||||
sh 'docker-compose up cypress-mysql'
|
||||
// Get results
|
||||
sh 'docker cp -L "$(docker-compose ps -q cypress-mysql):/test/results" test/'
|
||||
}
|
||||
post {
|
||||
always {
|
||||
// Dumps to analyze later
|
||||
sh 'mkdir -p debug'
|
||||
sh 'docker-compose logs fullstack-mysql | gzip > debug/docker_fullstack_mysql.log.gz'
|
||||
sh 'docker-compose logs db | gzip > debug/docker_db.log.gz'
|
||||
// Cypress videos and screenshot artifacts
|
||||
dir(path: 'test/results') {
|
||||
@ -121,18 +143,16 @@ pipeline {
|
||||
}
|
||||
}
|
||||
steps {
|
||||
ansiColor('xterm') {
|
||||
dir(path: 'docs') {
|
||||
sh 'yarn install'
|
||||
sh 'yarn build'
|
||||
}
|
||||
|
||||
dir(path: 'docs/.vuepress/dist') {
|
||||
sh 'tar -czf ../../docs.tgz *'
|
||||
}
|
||||
|
||||
archiveArtifacts(artifacts: 'docs/docs.tgz', allowEmptyArchive: false)
|
||||
dir(path: 'docs') {
|
||||
sh 'yarn install'
|
||||
sh 'yarn build'
|
||||
}
|
||||
|
||||
dir(path: 'docs/.vuepress/dist') {
|
||||
sh 'tar -czf ../../docs.tgz *'
|
||||
}
|
||||
|
||||
archiveArtifacts(artifacts: 'docs/docs.tgz', allowEmptyArchive: false)
|
||||
}
|
||||
}
|
||||
stage('MultiArch Build') {
|
||||
@ -142,12 +162,11 @@ pipeline {
|
||||
}
|
||||
}
|
||||
steps {
|
||||
ansiColor('xterm') {
|
||||
withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) {
|
||||
sh "docker login -u '${duser}' -p '${dpass}'"
|
||||
// Buildx with push
|
||||
sh "./scripts/buildx --push ${BUILDX_PUSH_TAGS}"
|
||||
}
|
||||
withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) {
|
||||
// Docker Login
|
||||
sh "docker login -u '${duser}' -p '${dpass}'"
|
||||
// Buildx with push from cache
|
||||
sh "./scripts/buildx --push ${BUILDX_PUSH_TAGS}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -193,10 +212,8 @@ pipeline {
|
||||
}
|
||||
}
|
||||
steps {
|
||||
ansiColor('xterm') {
|
||||
script {
|
||||
def comment = pullRequest.comment("Docker Image for build ${BUILD_NUMBER} is available on [DockerHub](https://cloud.docker.com/repository/docker/jc21/${IMAGE}) as `jc21/${IMAGE}:github-${BRANCH_LOWER}`")
|
||||
}
|
||||
script {
|
||||
def comment = pullRequest.comment("Docker Image for build ${BUILD_NUMBER} is available on [DockerHub](https://cloud.docker.com/repository/docker/jc21/${IMAGE}) as `jc21/${IMAGE}:github-${BRANCH_LOWER}`")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
185
README.md
185
README.md
@ -1,16 +1,19 @@
|
||||
<p align="center">
|
||||
<img src="https://nginxproxymanager.com/github.png">
|
||||
<br><br>
|
||||
<img src="https://img.shields.io/badge/version-2.2.2-green.svg?style=for-the-badge">
|
||||
<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">
|
||||
</a>
|
||||
<a href="https://hub.docker.com/repository/docker/jc21/nginx-proxy-manager">
|
||||
<img src="https://img.shields.io/docker/pulls/jc21/nginx-proxy-manager.svg?style=for-the-badge">
|
||||
</a>
|
||||
<a href="https://ci.nginxproxymanager.com/blue/organizations/jenkins/nginx-proxy-manager/branches/">
|
||||
<img src="https://img.shields.io/jenkins/build?jobUrl=https%3A%2F%2Fci.nginxproxymanager.com%2Fjob%2Fnginx-proxy-manager%2Fjob%2Fmaster&style=for-the-badge">
|
||||
</a>
|
||||
<img src="https://nginxproxymanager.com/github.png">
|
||||
<br><br>
|
||||
<img src="https://img.shields.io/badge/version-2.6.1-green.svg?style=for-the-badge">
|
||||
<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">
|
||||
</a>
|
||||
<a href="https://hub.docker.com/repository/docker/jc21/nginx-proxy-manager">
|
||||
<img src="https://img.shields.io/docker/pulls/jc21/nginx-proxy-manager.svg?style=for-the-badge">
|
||||
</a>
|
||||
<a href="https://ci.nginxproxymanager.com/blue/organizations/jenkins/nginx-proxy-manager/branches/">
|
||||
<img src="https://img.shields.io/jenkins/build?jobUrl=https%3A%2F%2Fci.nginxproxymanager.com%2Fjob%2Fnginx-proxy-manager%2Fjob%2Fmaster&style=for-the-badge">
|
||||
</a>
|
||||
<a href="https://gitter.im/nginx-proxy-manager/community">
|
||||
<img alt="Gitter" src="https://img.shields.io/gitter/room/nginx-proxy-manager/community?style=for-the-badge">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
This project comes as a pre-built docker image that enables you to easily forward to your websites
|
||||
@ -48,3 +51,161 @@ I won't go in to too much detail here but here are the basics for someone new to
|
||||
2. Add port forwarding for port 80 and 443 to the server hosting this project
|
||||
3. Configure your domain name details to point to your home, either with a static ip or a service like DuckDNS or [Amazon Route53](https://github.com/jc21/route53-ddns)
|
||||
4. Use the Nginx Proxy Manager as your gateway to forward to your other web based services
|
||||
|
||||
|
||||
## Contributors
|
||||
|
||||
Special thanks to the following contributors:
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
<!-- markdownlint-disable -->
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/Subv">
|
||||
<img src="https://avatars1.githubusercontent.com/u/357072?s=460&u=d8adcdc91d749ae53e177973ed9b6bb6c4c894a3&v=4" width="80px;" alt=""/>
|
||||
<br /><sub><b>Sebastian Valle</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/Indemnity83">
|
||||
<img src="https://avatars3.githubusercontent.com/u/35218?s=460&u=7082004ff35138157c868d7d9c683ccebfce5968&v=4" width="80px;" alt=""/>
|
||||
<br /><sub><b>Kyle Klaus</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/theraw">
|
||||
<img src="https://avatars1.githubusercontent.com/u/32969774?s=460&u=6b359971e15685fb0359e6a8c065a399b40dc228&v=4" width="80px;" alt=""/>
|
||||
<br /><sub><b>ƬHE ЯAW</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/spalger">
|
||||
<img src="https://avatars2.githubusercontent.com/u/1329312?s=400&u=565223e38f1c052afb4c5dcca3fcf1c63ba17ae7&v=4" width="80px;" alt=""/>
|
||||
<br /><sub><b>Spencer</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/Xantios">
|
||||
<img src="https://avatars3.githubusercontent.com/u/1507836?s=460&v=4" width="80px;" alt=""/>
|
||||
<br /><sub><b>Xantios Krugor</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/dpanesso">
|
||||
<img src="https://avatars2.githubusercontent.com/u/2687121?s=460&v=4" width="80px;" alt=""/>
|
||||
<br /><sub><b>David Panesso</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/IronTooch">
|
||||
<img src="https://avatars3.githubusercontent.com/u/27360514?s=460&u=69bf854a6647c55725f62ecb8d39249c6c0b2602&v=4" width="80px;" alt=""/>
|
||||
<br /><sub><b>IronTooch</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/damianog">
|
||||
<img src="https://avatars1.githubusercontent.com/u/2786682?s=460&u=76c6136fae797abb76b951cd8a246dcaecaf21af&v=4" width="80px;" alt=""/>
|
||||
<br /><sub><b>Damiano</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/tfmm">
|
||||
<img src="https://avatars3.githubusercontent.com/u/6880538?s=460&u=ce0160821cc4aa802df8395200f2d4956a5bc541&v=4" width="80px;" alt=""/>
|
||||
<br /><sub><b>Russ</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/margaale">
|
||||
<img src="https://avatars3.githubusercontent.com/u/20794934?s=460&v=4" width="80px;" alt=""/>
|
||||
<br /><sub><b>Marcelo Castagna</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/Steven-Harris">
|
||||
<img src="https://avatars2.githubusercontent.com/u/7720242?s=460&v=4" width="80px;" alt=""/>
|
||||
<br /><sub><b>Steven Harris</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/jlesage">
|
||||
<img src="https://avatars0.githubusercontent.com/u/1791123?s=460&v=4" width="80px;" alt=""/>
|
||||
<br /><sub><b>Jocelyn Le Sage</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/cmer">
|
||||
<img src="https://avatars0.githubusercontent.com/u/412?s=460&u=67dd8b2e3661bfd6f68ec1eaa5b9821bd8a321cd&v=4" width="80px;" alt=""/>
|
||||
<br /><sub><b>Carl Mercier</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/the1ts">
|
||||
<img src="https://avatars1.githubusercontent.com/u/84956?s=460&v=4" width="80px;" alt=""/>
|
||||
<br /><sub><b>Paul Mansfield</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/OhHeyAlan">
|
||||
<img src="https://avatars0.githubusercontent.com/u/11955126?s=460&u=fbaa5a1a4f73ef8960132c703349bfd037fe2630&v=4" width="80px;" alt=""/>
|
||||
<br /><sub><b>OhHeyAlan</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/dogmatic69">
|
||||
<img src="https://avatars2.githubusercontent.com/u/94674?s=460&u=ca7647de53145c6283b6373ade5dc94ba99347db&v=4" width="80px;" alt=""/>
|
||||
<br /><sub><b>Carl Sutton</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/tg44">
|
||||
<img src="https://avatars0.githubusercontent.com/u/31839?s=460&u=ad32f4cadfef5e5fb09cdfa4b7b7b36a99ba6811&v=4" width="80px;" alt=""/>
|
||||
<br /><sub><b>Gergő Törcsvári</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/vrenjith">
|
||||
<img src="https://avatars3.githubusercontent.com/u/2093241?s=460&u=96ce93a9bebabdd0a60a2dc96cd093a41d5edaba&v=4" width="80px;" alt=""/>
|
||||
<br /><sub><b>vrenjith</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/duhruh">
|
||||
<img src="https://avatars2.githubusercontent.com/u/1133969?s=460&u=c0691e6131ec6d516416c1c6fcedb5034f877bbe&v=4" width="80px;" alt=""/>
|
||||
<br /><sub><b>David Rivera</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/jipjan">
|
||||
<img src="https://avatars2.githubusercontent.com/u/1384618?s=460&v=4" width="80px;" alt=""/>
|
||||
<br /><sub><b>Jaap-Jan de Wit</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/jmwebslave">
|
||||
<img src="https://avatars2.githubusercontent.com/u/6118262?s=460&u=7db409c47135b1e141c366bbb03ed9fae6ac2638&v=4" width="80px;" alt=""/>
|
||||
<br /><sub><b>James Morgan</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/chaptergy">
|
||||
<img src="https://avatars2.githubusercontent.com/u/26956711?s=460&u=7d9adebabb6b4e7af7cb05d98d751087a372304b&v=4" width="80px;" alt=""/>
|
||||
<br /><sub><b>chaptergy</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/Philip-Mooney">
|
||||
<img src="https://avatars0.githubusercontent.com/u/48624631?s=460&v=4" width="80px;" alt=""/>
|
||||
<br /><sub><b>Philip Mooney</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- markdownlint-enable -->
|
||||
<!-- prettier-ignore-end -->
|
||||
|
@ -66,7 +66,7 @@ app.use(function (err, req, res, next) {
|
||||
}
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
if (process.env.NODE_ENV === 'development' || (req.baseUrl + req.path).includes('nginx/certificates')) {
|
||||
payload.debug = {
|
||||
stack: typeof err.stack !== 'undefined' && err.stack ? err.stack.split('\n') : null,
|
||||
previous: err.previous
|
||||
|
26
backend/config/sqlite-test-db.json
Normal file
26
backend/config/sqlite-test-db.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"database": {
|
||||
"engine": "knex-native",
|
||||
"knex": {
|
||||
"client": "sqlite3",
|
||||
"connection": {
|
||||
"filename": "/app/config/mydb.sqlite"
|
||||
},
|
||||
"pool": {
|
||||
"min": 0,
|
||||
"max": 1,
|
||||
"createTimeoutMillis": 3000,
|
||||
"acquireTimeoutMillis": 30000,
|
||||
"idleTimeoutMillis": 30000,
|
||||
"reapIntervalMillis": 1000,
|
||||
"createRetryIntervalMillis": 100,
|
||||
"propagateCreateError": false
|
||||
},
|
||||
"migrations": {
|
||||
"tableName": "migrations",
|
||||
"stub": "src/backend/lib/migrate_template.js",
|
||||
"directory": "src/backend/migrations"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,19 +4,27 @@ if (!config.has('database')) {
|
||||
throw new Error('Database config does not exist! Please read the instructions: https://github.com/jc21/nginx-proxy-manager/blob/master/doc/INSTALL.md');
|
||||
}
|
||||
|
||||
let data = {
|
||||
client: config.database.engine,
|
||||
connection: {
|
||||
host: config.database.host,
|
||||
user: config.database.user,
|
||||
password: config.database.password,
|
||||
database: config.database.name,
|
||||
port: config.database.port
|
||||
},
|
||||
migrations: {
|
||||
tableName: 'migrations'
|
||||
}
|
||||
};
|
||||
function generateDbConfig() {
|
||||
if (config.database.engine === 'knex-native') {
|
||||
return config.database.knex;
|
||||
} else
|
||||
return {
|
||||
client: config.database.engine,
|
||||
connection: {
|
||||
host: config.database.host,
|
||||
user: config.database.user,
|
||||
password: config.database.password,
|
||||
database: config.database.name,
|
||||
port: config.database.port
|
||||
},
|
||||
migrations: {
|
||||
tableName: 'migrations'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
let data = generateDbConfig();
|
||||
|
||||
if (typeof config.database.version !== 'undefined') {
|
||||
data.version = config.database.version;
|
||||
|
@ -1,14 +1,15 @@
|
||||
const _ = require('lodash');
|
||||
const fs = require('fs');
|
||||
const batchflow = require('batchflow');
|
||||
const logger = require('../logger').access;
|
||||
const error = require('../lib/error');
|
||||
const accessListModel = require('../models/access_list');
|
||||
const accessListAuthModel = require('../models/access_list_auth');
|
||||
const proxyHostModel = require('../models/proxy_host');
|
||||
const internalAuditLog = require('./audit-log');
|
||||
const internalNginx = require('./nginx');
|
||||
const utils = require('../lib/utils');
|
||||
const _ = require('lodash');
|
||||
const fs = require('fs');
|
||||
const batchflow = require('batchflow');
|
||||
const logger = require('../logger').access;
|
||||
const error = require('../lib/error');
|
||||
const accessListModel = require('../models/access_list');
|
||||
const accessListAuthModel = require('../models/access_list_auth');
|
||||
const accessListClientModel = require('../models/access_list_client');
|
||||
const proxyHostModel = require('../models/proxy_host');
|
||||
const internalAuditLog = require('./audit-log');
|
||||
const internalNginx = require('./nginx');
|
||||
const utils = require('../lib/utils');
|
||||
|
||||
function omissions () {
|
||||
return ['is_deleted'];
|
||||
@ -29,14 +30,17 @@ const internalAccessList = {
|
||||
.omit(omissions())
|
||||
.insertAndFetch({
|
||||
name: data.name,
|
||||
satisfy_any: data.satisfy_any,
|
||||
pass_auth: data.pass_auth,
|
||||
owner_user_id: access.token.getUserId(1)
|
||||
});
|
||||
})
|
||||
.then((row) => {
|
||||
data.id = row.id;
|
||||
|
||||
// Now add the items
|
||||
let promises = [];
|
||||
|
||||
// Now add the items
|
||||
data.items.map((item) => {
|
||||
promises.push(accessListAuthModel
|
||||
.query()
|
||||
@ -48,13 +52,27 @@ const internalAccessList = {
|
||||
);
|
||||
});
|
||||
|
||||
// Now add the clients
|
||||
if (typeof data.clients !== 'undefined' && data.clients) {
|
||||
data.clients.map((client) => {
|
||||
promises.push(accessListClientModel
|
||||
.query()
|
||||
.insert({
|
||||
access_list_id: row.id,
|
||||
address: client.address,
|
||||
directive: client.directive
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
})
|
||||
.then(() => {
|
||||
// re-fetch with expansions
|
||||
return internalAccessList.get(access, {
|
||||
id: data.id,
|
||||
expand: ['owner', 'items']
|
||||
expand: ['owner', 'items', 'clients', 'proxy_hosts.access_list.[clients,items]']
|
||||
}, true /* <- skip masking */);
|
||||
})
|
||||
.then((row) => {
|
||||
@ -64,7 +82,7 @@ const internalAccessList = {
|
||||
return internalAccessList.build(row)
|
||||
.then(() => {
|
||||
if (row.proxy_host_count) {
|
||||
return internalNginx.reload();
|
||||
return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
@ -109,7 +127,9 @@ const internalAccessList = {
|
||||
.query()
|
||||
.where({id: data.id})
|
||||
.patch({
|
||||
name: data.name
|
||||
name: data.name,
|
||||
satisfy_any: data.satisfy_any,
|
||||
pass_auth: data.pass_auth,
|
||||
});
|
||||
}
|
||||
})
|
||||
@ -153,6 +173,38 @@ const internalAccessList = {
|
||||
});
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
// Check for clients and add/update/remove them
|
||||
if (typeof data.clients !== 'undefined' && data.clients) {
|
||||
let promises = [];
|
||||
|
||||
data.clients.map(function (client) {
|
||||
if (client.address) {
|
||||
promises.push(accessListClientModel
|
||||
.query()
|
||||
.insert({
|
||||
access_list_id: data.id,
|
||||
address: client.address,
|
||||
directive: client.directive
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
let query = accessListClientModel
|
||||
.query()
|
||||
.delete()
|
||||
.where('access_list_id', data.id);
|
||||
|
||||
return query
|
||||
.then(() => {
|
||||
// Add new items
|
||||
if (promises.length) {
|
||||
return Promise.all(promises);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
// Add to audit log
|
||||
return internalAuditLog.add(access, {
|
||||
@ -166,14 +218,14 @@ const internalAccessList = {
|
||||
// re-fetch with expansions
|
||||
return internalAccessList.get(access, {
|
||||
id: data.id,
|
||||
expand: ['owner', 'items']
|
||||
expand: ['owner', 'items', 'clients', 'proxy_hosts.access_list.[clients,items]']
|
||||
}, true /* <- skip masking */);
|
||||
})
|
||||
.then((row) => {
|
||||
return internalAccessList.build(row)
|
||||
.then(() => {
|
||||
if (row.proxy_host_count) {
|
||||
return internalNginx.reload();
|
||||
return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
@ -204,7 +256,7 @@ const internalAccessList = {
|
||||
.joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0')
|
||||
.where('access_list.is_deleted', 0)
|
||||
.andWhere('access_list.id', data.id)
|
||||
.allowEager('[owner,items,proxy_hosts]')
|
||||
.allowEager('[owner,items,clients,proxy_hosts.[*, access_list.[clients,items]]]')
|
||||
.omit(['access_list.is_deleted'])
|
||||
.first();
|
||||
|
||||
@ -246,7 +298,7 @@ const internalAccessList = {
|
||||
delete: (access, data) => {
|
||||
return access.can('access_lists:delete', data.id)
|
||||
.then(() => {
|
||||
return internalAccessList.get(access, {id: data.id, expand: ['proxy_hosts', 'items']});
|
||||
return internalAccessList.get(access, {id: data.id, expand: ['proxy_hosts', 'items', 'clients']});
|
||||
})
|
||||
.then((row) => {
|
||||
if (!row) {
|
||||
@ -330,11 +382,11 @@ const internalAccessList = {
|
||||
.where('access_list.is_deleted', 0)
|
||||
.groupBy('access_list.id')
|
||||
.omit(['access_list.is_deleted'])
|
||||
.allowEager('[owner,items]')
|
||||
.allowEager('[owner,items,clients]')
|
||||
.orderBy('access_list.name', 'ASC');
|
||||
|
||||
if (access_data.permission_visibility !== 'all') {
|
||||
query.andWhere('owner_user_id', access.token.getUserId(1));
|
||||
query.andWhere('access_list.owner_user_id', access.token.getUserId(1));
|
||||
}
|
||||
|
||||
// Query is used for searching
|
||||
|
@ -13,6 +13,7 @@ const internalNginx = require('./nginx');
|
||||
const internalHost = require('./host');
|
||||
const certbot_command = '/usr/bin/certbot';
|
||||
const le_config = '/etc/letsencrypt.ini';
|
||||
const dns_plugins = require('../global/certbot-dns-plugins');
|
||||
|
||||
function omissions() {
|
||||
return ['is_deleted'];
|
||||
@ -77,7 +78,7 @@ const internalCertificate = {
|
||||
.where('id', certificate.id)
|
||||
.andWhere('provider', 'letsencrypt')
|
||||
.patch({
|
||||
expires_on: certificateModel.raw('FROM_UNIXTIME(' + cert_info.dates.to + ')')
|
||||
expires_on: moment(cert_info.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss')
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
@ -141,36 +142,60 @@ const internalCertificate = {
|
||||
});
|
||||
})
|
||||
.then((in_use_result) => {
|
||||
// 3. Generate the LE config
|
||||
return internalNginx.generateLetsEncryptRequestConfig(certificate)
|
||||
.then(internalNginx.reload)
|
||||
.then(() => {
|
||||
// With DNS challenge no config is needed, so skip 3 and 5.
|
||||
if (certificate.meta.dns_challenge) {
|
||||
return internalNginx.reload().then(() => {
|
||||
// 4. Request cert
|
||||
return internalCertificate.requestLetsEncryptSsl(certificate);
|
||||
return internalCertificate.requestLetsEncryptSslWithDnsChallenge(certificate);
|
||||
})
|
||||
.then(() => {
|
||||
// 5. Remove LE config
|
||||
return internalNginx.deleteLetsEncryptRequestConfig(certificate);
|
||||
})
|
||||
.then(internalNginx.reload)
|
||||
.then(() => {
|
||||
// 6. Re-instate previously disabled hosts
|
||||
return internalCertificate.enableInUseHosts(in_use_result);
|
||||
})
|
||||
.then(() => {
|
||||
return certificate;
|
||||
})
|
||||
.catch((err) => {
|
||||
// In the event of failure, revert things and throw err back
|
||||
return internalNginx.deleteLetsEncryptRequestConfig(certificate)
|
||||
.then(() => {
|
||||
return internalCertificate.enableInUseHosts(in_use_result);
|
||||
})
|
||||
.then(internalNginx.reload)
|
||||
.then(() => {
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
.then(internalNginx.reload)
|
||||
.then(() => {
|
||||
// 6. Re-instate previously disabled hosts
|
||||
return internalCertificate.enableInUseHosts(in_use_result);
|
||||
})
|
||||
.then(() => {
|
||||
return certificate;
|
||||
})
|
||||
.catch((err) => {
|
||||
// In the event of failure, revert things and throw err back
|
||||
return internalCertificate.enableInUseHosts(in_use_result)
|
||||
.then(internalNginx.reload)
|
||||
.then(() => {
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// 3. Generate the LE config
|
||||
return internalNginx.generateLetsEncryptRequestConfig(certificate)
|
||||
.then(internalNginx.reload)
|
||||
.then(() => {
|
||||
// 4. Request cert
|
||||
return internalCertificate.requestLetsEncryptSsl(certificate);
|
||||
})
|
||||
.then(() => {
|
||||
// 5. Remove LE config
|
||||
return internalNginx.deleteLetsEncryptRequestConfig(certificate);
|
||||
})
|
||||
.then(internalNginx.reload)
|
||||
.then(() => {
|
||||
// 6. Re-instate previously disabled hosts
|
||||
return internalCertificate.enableInUseHosts(in_use_result);
|
||||
})
|
||||
.then(() => {
|
||||
return certificate;
|
||||
})
|
||||
.catch((err) => {
|
||||
// In the event of failure, revert things and throw err back
|
||||
return internalNginx.deleteLetsEncryptRequestConfig(certificate)
|
||||
.then(() => {
|
||||
return internalCertificate.enableInUseHosts(in_use_result);
|
||||
})
|
||||
.then(internalNginx.reload)
|
||||
.then(() => {
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
// At this point, the letsencrypt cert should exist on disk.
|
||||
@ -180,7 +205,7 @@ const internalCertificate = {
|
||||
return certificateModel
|
||||
.query()
|
||||
.patchAndFetchById(certificate.id, {
|
||||
expires_on: certificateModel.raw('FROM_UNIXTIME(' + cert_info.dates.to + ')')
|
||||
expires_on: moment(cert_info.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss')
|
||||
})
|
||||
.then((saved_row) => {
|
||||
// Add cert data for audit log
|
||||
@ -558,7 +583,7 @@ const internalCertificate = {
|
||||
// TODO: This uses a mysql only raw function that won't translate to postgres
|
||||
return internalCertificate.update(access, {
|
||||
id: data.id,
|
||||
expires_on: certificateModel.raw('FROM_UNIXTIME(' + validations.certificate.dates.to + ')'),
|
||||
expires_on: moment(validations.certificate.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss'),
|
||||
domain_names: [validations.certificate.cn],
|
||||
meta: _.clone(row.meta) // Prevent the update method from changing this value that we'll use later
|
||||
})
|
||||
@ -733,7 +758,6 @@ const internalCertificate = {
|
||||
'--agree-tos ' +
|
||||
'--email "' + certificate.meta.letsencrypt_email + '" ' +
|
||||
'--preferred-challenges "dns,http" ' +
|
||||
'--webroot ' +
|
||||
'--domains "' + certificate.domain_names.join(',') + '" ' +
|
||||
(le_staging ? '--staging' : '');
|
||||
|
||||
@ -748,6 +772,74 @@ const internalCertificate = {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Object} certificate the certificate row
|
||||
* @param {String} dns_provider the dns provider name (key used in `certbot-dns-plugins.js`)
|
||||
* @param {String | null} credentials the content of this providers credentials file
|
||||
* @param {String} propagation_seconds the cloudflare api token
|
||||
* @returns {Promise}
|
||||
*/
|
||||
requestLetsEncryptSslWithDnsChallenge: (certificate) => {
|
||||
const dns_plugin = dns_plugins[certificate.meta.dns_provider];
|
||||
|
||||
if (!dns_plugin) {
|
||||
throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`);
|
||||
}
|
||||
|
||||
logger.info(`Requesting Let'sEncrypt certificates via ${dns_plugin.display_name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);
|
||||
|
||||
const credentials_loc = '/etc/letsencrypt/credentials-' + certificate.id;
|
||||
const credentials_cmd = 'echo \'' + certificate.meta.dns_provider_credentials.replace('\'', '\\\'') + '\' > \'' + credentials_loc + '\' && chmod 600 \'' + credentials_loc + '\'';
|
||||
const prepare_cmd = 'pip3 install ' + dns_plugin.package_name + '==' + dns_plugin.package_version;
|
||||
|
||||
// Whether the plugin has a --<name>-credentials argument
|
||||
const has_config_arg = certificate.meta.dns_provider !== 'route53';
|
||||
|
||||
let main_cmd =
|
||||
certbot_command + ' certonly --non-interactive ' +
|
||||
'--cert-name "npm-' + certificate.id + '" ' +
|
||||
'--agree-tos ' +
|
||||
'--email "' + certificate.meta.letsencrypt_email + '" ' +
|
||||
'--domains "' + certificate.domain_names.join(',') + '" ' +
|
||||
'--authenticator ' + dns_plugin.full_plugin_name + ' ' +
|
||||
(
|
||||
has_config_arg
|
||||
? '--' + dns_plugin.full_plugin_name + '-credentials "' + credentials_loc + '"'
|
||||
: ''
|
||||
) +
|
||||
(
|
||||
certificate.meta.propagation_seconds !== undefined
|
||||
? ' --' + dns_plugin.full_plugin_name + '-propagation-seconds ' + certificate.meta.propagation_seconds
|
||||
: ''
|
||||
) +
|
||||
(le_staging ? ' --staging' : '');
|
||||
|
||||
// Prepend the path to the credentials file as an environment variable
|
||||
if (certificate.meta.dns_provider === 'route53') {
|
||||
main_cmd = 'AWS_CONFIG_FILE=\'' + credentials_loc + '\' ' + main_cmd;
|
||||
}
|
||||
|
||||
const teardown_cmd = `rm '${credentials_loc}'`;
|
||||
|
||||
if (debug_mode) {
|
||||
logger.info('Command:', `${credentials_cmd} && ${prepare_cmd} && ${main_cmd} && ${teardown_cmd}`);
|
||||
}
|
||||
|
||||
return utils.exec(credentials_cmd)
|
||||
.then(() => {
|
||||
return utils.exec(prepare_cmd)
|
||||
.then(() => {
|
||||
return utils.exec(main_cmd)
|
||||
.then(async (result) => {
|
||||
await utils.exec(teardown_cmd);
|
||||
logger.info(result);
|
||||
return result;
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @param {Access} access
|
||||
* @param {Object} data
|
||||
@ -761,7 +853,9 @@ const internalCertificate = {
|
||||
})
|
||||
.then((certificate) => {
|
||||
if (certificate.provider === 'letsencrypt') {
|
||||
return internalCertificate.renewLetsEncryptSsl(certificate)
|
||||
let renewMethod = certificate.meta.dns_challenge ? internalCertificate.renewLetsEncryptSslWithDnsChallenge : internalCertificate.renewLetsEncryptSsl;
|
||||
|
||||
return renewMethod(certificate)
|
||||
.then(() => {
|
||||
return internalCertificate.getCertificateInfoFromFile('/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem');
|
||||
})
|
||||
@ -769,7 +863,7 @@ const internalCertificate = {
|
||||
return certificateModel
|
||||
.query()
|
||||
.patchAndFetchById(certificate.id, {
|
||||
expires_on: certificateModel.raw('FROM_UNIXTIME(' + cert_info.dates.to + ')')
|
||||
expires_on: moment(cert_info.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss')
|
||||
});
|
||||
})
|
||||
.then((updated_certificate) => {
|
||||
@ -815,6 +909,54 @@ const internalCertificate = {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Object} certificate the certificate row
|
||||
* @returns {Promise}
|
||||
*/
|
||||
renewLetsEncryptSslWithDnsChallenge: (certificate) => {
|
||||
const dns_plugin = dns_plugins[certificate.meta.dns_provider];
|
||||
|
||||
if (!dns_plugin) {
|
||||
throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`);
|
||||
}
|
||||
|
||||
logger.info(`Renewing Let'sEncrypt certificates via ${dns_plugin.display_name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);
|
||||
|
||||
const credentials_loc = '/etc/letsencrypt/credentials-' + certificate.id;
|
||||
const credentials_cmd = 'echo \'' + certificate.meta.dns_provider_credentials.replace('\'', '\\\'') + '\' > \'' + credentials_loc + '\' && chmod 600 \'' + credentials_loc + '\'';
|
||||
const prepare_cmd = 'pip3 install ' + dns_plugin.package_name + '==' + dns_plugin.package_version;
|
||||
|
||||
let main_cmd =
|
||||
certbot_command + ' renew --non-interactive ' +
|
||||
'--cert-name "npm-' + certificate.id + '" ' +
|
||||
'--disable-hook-validation' +
|
||||
(le_staging ? ' --staging' : '');
|
||||
|
||||
// Prepend the path to the credentials file as an environment variable
|
||||
if (certificate.meta.dns_provider === 'route53') {
|
||||
main_cmd = 'AWS_CONFIG_FILE=\'' + credentials_loc + '\' ' + main_cmd;
|
||||
}
|
||||
|
||||
const teardown_cmd = `rm '${credentials_loc}'`;
|
||||
|
||||
if (debug_mode) {
|
||||
logger.info('Command:', `${credentials_cmd} && ${prepare_cmd} && ${main_cmd} && ${teardown_cmd}`);
|
||||
}
|
||||
|
||||
return utils.exec(credentials_cmd)
|
||||
.then(() => {
|
||||
return utils.exec(prepare_cmd)
|
||||
.then(() => {
|
||||
return utils.exec(main_cmd)
|
||||
.then(async (result) => {
|
||||
await utils.exec(teardown_cmd);
|
||||
logger.info(result);
|
||||
return result;
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Object} certificate the certificate row
|
||||
* @param {Boolean} [throw_errors]
|
||||
@ -824,7 +966,6 @@ const internalCertificate = {
|
||||
logger.info('Revoking Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
|
||||
|
||||
let cmd = certbot_command + ' revoke --non-interactive ' +
|
||||
'--config "' + le_config + '" ' +
|
||||
'--cert-path "/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem" ' +
|
||||
'--delete-after-revoke ' +
|
||||
(le_staging ? '--staging' : '');
|
||||
|
@ -273,6 +273,7 @@ const internalNginx = {
|
||||
return new Promise((resolve, reject) => {
|
||||
let template = null;
|
||||
let filename = '/data/nginx/temp/letsencrypt_' + certificate.id + '.conf';
|
||||
|
||||
try {
|
||||
template = fs.readFileSync(__dirname + '/../templates/letsencrypt-request.conf', {encoding: 'utf8'});
|
||||
} catch (err) {
|
||||
@ -280,6 +281,8 @@ const internalNginx = {
|
||||
return;
|
||||
}
|
||||
|
||||
certificate.ipv6 = internalNginx.ipv6Enabled();
|
||||
|
||||
renderEngine
|
||||
.parseAndRender(template, certificate)
|
||||
.then((config_text) => {
|
||||
|
@ -73,7 +73,7 @@ const internalProxyHost = {
|
||||
// re-fetch with cert
|
||||
return internalProxyHost.get(access, {
|
||||
id: row.id,
|
||||
expand: ['certificate', 'owner', 'access_list']
|
||||
expand: ['certificate', 'owner', 'access_list.[clients,items]']
|
||||
});
|
||||
})
|
||||
.then((row) => {
|
||||
@ -186,7 +186,7 @@ const internalProxyHost = {
|
||||
.then(() => {
|
||||
return internalProxyHost.get(access, {
|
||||
id: data.id,
|
||||
expand: ['owner', 'certificate', 'access_list']
|
||||
expand: ['owner', 'certificate', 'access_list.[clients,items]']
|
||||
})
|
||||
.then((row) => {
|
||||
// Configure nginx
|
||||
@ -219,7 +219,7 @@ const internalProxyHost = {
|
||||
.query()
|
||||
.where('is_deleted', 0)
|
||||
.andWhere('id', data.id)
|
||||
.allowEager('[owner,access_list,certificate]')
|
||||
.allowEager('[owner,access_list,access_list.[clients,items],certificate]')
|
||||
.first();
|
||||
|
||||
if (access_data.permission_visibility !== 'all') {
|
||||
|
@ -4,11 +4,21 @@ module.exports = function (req, res, next) {
|
||||
|
||||
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({
|
||||
type: 'string',
|
||||
pattern: '^[a-z\\-]+:\\/\\/(?:[\\w\\-\\.]+(:[0-9]+)?/?)?$'
|
||||
}, req.headers.origin)
|
||||
validator(originSchema, req.headers.origin)
|
||||
.then(function () {
|
||||
res.set({
|
||||
'Access-Control-Allow-Origin': req.headers.origin,
|
||||
|
@ -22,22 +22,6 @@ exports.up = function (knex/*, Promise*/) {
|
||||
})
|
||||
.then(() => {
|
||||
logger.info('[' + migrate_name + '] setting Table created');
|
||||
|
||||
// TODO: add settings
|
||||
let settingModel = require('../models/setting');
|
||||
|
||||
return settingModel
|
||||
.query()
|
||||
.insert({
|
||||
id: 'default-site',
|
||||
name: 'Default Site',
|
||||
description: 'What to show when Nginx is hit with an unknown Host',
|
||||
value: 'congratulations',
|
||||
meta: {}
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
logger.info('[' + migrate_name + '] Default settings added');
|
||||
});
|
||||
};
|
||||
|
||||
|
53
backend/migrations/20200410143839_access_list_client.js
Normal file
53
backend/migrations/20200410143839_access_list_client.js
Normal file
@ -0,0 +1,53 @@
|
||||
const migrate_name = 'access_list_client';
|
||||
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.createTable('access_list_client', (table) => {
|
||||
table.increments().primary();
|
||||
table.dateTime('created_on').notNull();
|
||||
table.dateTime('modified_on').notNull();
|
||||
table.integer('access_list_id').notNull().unsigned();
|
||||
table.string('address').notNull();
|
||||
table.string('directive').notNull();
|
||||
table.json('meta').notNull();
|
||||
|
||||
})
|
||||
.then(function () {
|
||||
logger.info('[' + migrate_name + '] access_list_client Table created');
|
||||
|
||||
return knex.schema.table('access_list', function (access_list) {
|
||||
access_list.integer('satify_any').notNull().defaultTo(0);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
logger.info('[' + migrate_name + '] access_list Table altered');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Undo Migrate
|
||||
*
|
||||
* @param {Object} knex
|
||||
* @param {Promise} Promise
|
||||
* @returns {Promise}
|
||||
*/
|
||||
exports.down = function (knex/*, Promise*/) {
|
||||
logger.info('[' + migrate_name + '] Migrating Down...');
|
||||
|
||||
return knex.schema.dropTable('access_list_client')
|
||||
.then(() => {
|
||||
logger.info('[' + migrate_name + '] access_list_client Table dropped');
|
||||
});
|
||||
};
|
34
backend/migrations/20200410143840_access_list_client_fix.js
Normal file
34
backend/migrations/20200410143840_access_list_client_fix.js
Normal file
@ -0,0 +1,34 @@
|
||||
const migrate_name = 'access_list_client_fix';
|
||||
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('access_list', function (access_list) {
|
||||
access_list.renameColumn('satify_any', 'satisfy_any');
|
||||
})
|
||||
.then(() => {
|
||||
logger.info('[' + migrate_name + '] access_list Table altered');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Undo Migrate
|
||||
*
|
||||
* @param {Object} knex
|
||||
* @param {Promise} Promise
|
||||
* @returns {Promise}
|
||||
*/
|
||||
exports.down = function (knex, Promise) {
|
||||
logger.warn('[' + migrate_name + '] You can\'t migrate down this one.');
|
||||
return Promise.resolve(true);
|
||||
};
|
41
backend/migrations/20201014143841_pass_auth.js
Normal file
41
backend/migrations/20201014143841_pass_auth.js
Normal file
@ -0,0 +1,41 @@
|
||||
const migrate_name = 'pass_auth';
|
||||
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('access_list', function (access_list) {
|
||||
access_list.integer('pass_auth').notNull().defaultTo(1);
|
||||
})
|
||||
.then(() => {
|
||||
logger.info('[' + migrate_name + '] access_list Table altered');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Undo Migrate
|
||||
*
|
||||
* @param {Object} knex
|
||||
* @param {Promise} Promise
|
||||
* @returns {Promise}
|
||||
*/
|
||||
exports.down = function (knex/*, Promise*/) {
|
||||
logger.info('[' + migrate_name + '] Migrating Down...');
|
||||
|
||||
return knex.schema.table('access_list', function (access_list) {
|
||||
access_list.dropColumn('pass_auth');
|
||||
})
|
||||
.then(() => {
|
||||
logger.info('[' + migrate_name + '] access_list pass_auth Column dropped');
|
||||
});
|
||||
};
|
@ -1,17 +1,19 @@
|
||||
// Objection Docs:
|
||||
// http://vincit.github.io/objection.js/
|
||||
|
||||
const db = require('../db');
|
||||
const Model = require('objection').Model;
|
||||
const User = require('./user');
|
||||
const AccessListAuth = require('./access_list_auth');
|
||||
const db = require('../db');
|
||||
const Model = require('objection').Model;
|
||||
const User = require('./user');
|
||||
const AccessListAuth = require('./access_list_auth');
|
||||
const AccessListClient = require('./access_list_client');
|
||||
const now = require('./now_helper');
|
||||
|
||||
Model.knex(db);
|
||||
|
||||
class AccessList extends Model {
|
||||
$beforeInsert () {
|
||||
this.created_on = Model.raw('NOW()');
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
this.created_on = now();
|
||||
this.modified_on = now();
|
||||
|
||||
// Default for meta
|
||||
if (typeof this.meta === 'undefined') {
|
||||
@ -20,7 +22,7 @@ class AccessList extends Model {
|
||||
}
|
||||
|
||||
$beforeUpdate () {
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
this.modified_on = now();
|
||||
}
|
||||
|
||||
static get name () {
|
||||
@ -62,6 +64,17 @@ class AccessList extends Model {
|
||||
qb.omit(['id', 'created_on', 'modified_on', 'access_list_id', 'meta']);
|
||||
}
|
||||
},
|
||||
clients: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: AccessListClient,
|
||||
join: {
|
||||
from: 'access_list.id',
|
||||
to: 'access_list_client.access_list_id'
|
||||
},
|
||||
modify: function (qb) {
|
||||
qb.omit(['id', 'created_on', 'modified_on', 'access_list_id', 'meta']);
|
||||
}
|
||||
},
|
||||
proxy_hosts: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: ProxyHost,
|
||||
@ -76,6 +89,14 @@ class AccessList extends Model {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
get satisfy() {
|
||||
return this.satisfy_any ? 'satisfy any' : 'satisfy all';
|
||||
}
|
||||
|
||||
get passauth() {
|
||||
return this.pass_auth ? '' : 'proxy_set_header Authorization "";';
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AccessList;
|
||||
|
@ -3,13 +3,14 @@
|
||||
|
||||
const db = require('../db');
|
||||
const Model = require('objection').Model;
|
||||
const now = require('./now_helper');
|
||||
|
||||
Model.knex(db);
|
||||
|
||||
class AccessListAuth extends Model {
|
||||
$beforeInsert () {
|
||||
this.created_on = Model.raw('NOW()');
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
this.created_on = now();
|
||||
this.modified_on = now();
|
||||
|
||||
// Default for meta
|
||||
if (typeof this.meta === 'undefined') {
|
||||
@ -18,7 +19,7 @@ class AccessListAuth extends Model {
|
||||
}
|
||||
|
||||
$beforeUpdate () {
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
this.modified_on = now();
|
||||
}
|
||||
|
||||
static get name () {
|
||||
|
59
backend/models/access_list_client.js
Normal file
59
backend/models/access_list_client.js
Normal file
@ -0,0 +1,59 @@
|
||||
// Objection Docs:
|
||||
// http://vincit.github.io/objection.js/
|
||||
|
||||
const db = require('../db');
|
||||
const Model = require('objection').Model;
|
||||
const now = require('./now_helper');
|
||||
|
||||
Model.knex(db);
|
||||
|
||||
class AccessListClient extends Model {
|
||||
$beforeInsert () {
|
||||
this.created_on = now();
|
||||
this.modified_on = now();
|
||||
|
||||
// Default for meta
|
||||
if (typeof this.meta === 'undefined') {
|
||||
this.meta = {};
|
||||
}
|
||||
}
|
||||
|
||||
$beforeUpdate () {
|
||||
this.modified_on = now();
|
||||
}
|
||||
|
||||
static get name () {
|
||||
return 'AccessListClient';
|
||||
}
|
||||
|
||||
static get tableName () {
|
||||
return 'access_list_client';
|
||||
}
|
||||
|
||||
static get jsonAttributes () {
|
||||
return ['meta'];
|
||||
}
|
||||
|
||||
static get relationMappings () {
|
||||
return {
|
||||
access_list: {
|
||||
relation: Model.HasOneRelation,
|
||||
modelClass: require('./access_list'),
|
||||
join: {
|
||||
from: 'access_list_client.access_list_id',
|
||||
to: 'access_list.id'
|
||||
},
|
||||
modify: function (qb) {
|
||||
qb.where('access_list.is_deleted', 0);
|
||||
qb.omit(['created_on', 'modified_on', 'is_deleted', 'access_list_id']);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
get rule() {
|
||||
return `${this.directive} ${this.address}`;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AccessListClient;
|
@ -4,13 +4,14 @@
|
||||
const db = require('../db');
|
||||
const Model = require('objection').Model;
|
||||
const User = require('./user');
|
||||
const now = require('./now_helper');
|
||||
|
||||
Model.knex(db);
|
||||
|
||||
class AuditLog extends Model {
|
||||
$beforeInsert () {
|
||||
this.created_on = Model.raw('NOW()');
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
this.created_on = now();
|
||||
this.modified_on = now();
|
||||
|
||||
// Default for meta
|
||||
if (typeof this.meta === 'undefined') {
|
||||
@ -19,7 +20,7 @@ class AuditLog extends Model {
|
||||
}
|
||||
|
||||
$beforeUpdate () {
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
this.modified_on = now();
|
||||
}
|
||||
|
||||
static get name () {
|
||||
|
@ -5,6 +5,7 @@ const bcrypt = require('bcrypt');
|
||||
const db = require('../db');
|
||||
const Model = require('objection').Model;
|
||||
const User = require('./user');
|
||||
const now = require('./now_helper');
|
||||
|
||||
Model.knex(db);
|
||||
|
||||
@ -24,8 +25,8 @@ function encryptPassword () {
|
||||
|
||||
class Auth extends Model {
|
||||
$beforeInsert (queryContext) {
|
||||
this.created_on = Model.raw('NOW()');
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
this.created_on = now();
|
||||
this.modified_on = now();
|
||||
|
||||
// Default for meta
|
||||
if (typeof this.meta === 'undefined') {
|
||||
@ -36,7 +37,7 @@ class Auth extends Model {
|
||||
}
|
||||
|
||||
$beforeUpdate (queryContext) {
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
this.modified_on = now();
|
||||
return encryptPassword.apply(this, queryContext);
|
||||
}
|
||||
|
||||
|
@ -4,17 +4,18 @@
|
||||
const db = require('../db');
|
||||
const Model = require('objection').Model;
|
||||
const User = require('./user');
|
||||
const now = require('./now_helper');
|
||||
|
||||
Model.knex(db);
|
||||
|
||||
class Certificate extends Model {
|
||||
$beforeInsert () {
|
||||
this.created_on = Model.raw('NOW()');
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
this.created_on = now();
|
||||
this.modified_on = now();
|
||||
|
||||
// Default for expires_on
|
||||
if (typeof this.expires_on === 'undefined') {
|
||||
this.expires_on = Model.raw('NOW()');
|
||||
this.expires_on = now();
|
||||
}
|
||||
|
||||
// Default for domain_names
|
||||
@ -31,7 +32,7 @@ class Certificate extends Model {
|
||||
}
|
||||
|
||||
$beforeUpdate () {
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
this.modified_on = now();
|
||||
|
||||
// Sort domain_names
|
||||
if (typeof this.domain_names !== 'undefined') {
|
||||
|
@ -5,13 +5,14 @@ const db = require('../db');
|
||||
const Model = require('objection').Model;
|
||||
const User = require('./user');
|
||||
const Certificate = require('./certificate');
|
||||
const now = require('./now_helper');
|
||||
|
||||
Model.knex(db);
|
||||
|
||||
class DeadHost extends Model {
|
||||
$beforeInsert () {
|
||||
this.created_on = Model.raw('NOW()');
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
this.created_on = now();
|
||||
this.modified_on = now();
|
||||
|
||||
// Default for domain_names
|
||||
if (typeof this.domain_names === 'undefined') {
|
||||
@ -27,7 +28,7 @@ class DeadHost extends Model {
|
||||
}
|
||||
|
||||
$beforeUpdate () {
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
this.modified_on = now();
|
||||
|
||||
// Sort domain_names
|
||||
if (typeof this.domain_names !== 'undefined') {
|
||||
|
13
backend/models/now_helper.js
Normal file
13
backend/models/now_helper.js
Normal file
@ -0,0 +1,13 @@
|
||||
const db = require('../db');
|
||||
const config = require('config');
|
||||
const Model = require('objection').Model;
|
||||
|
||||
Model.knex(db);
|
||||
|
||||
module.exports = function () {
|
||||
if (config.database.knex && config.database.knex.client === 'sqlite3') {
|
||||
return Model.raw('datetime(\'now\',\'localtime\')');
|
||||
} else {
|
||||
return Model.raw('NOW()');
|
||||
}
|
||||
};
|
@ -6,13 +6,14 @@ const Model = require('objection').Model;
|
||||
const User = require('./user');
|
||||
const AccessList = require('./access_list');
|
||||
const Certificate = require('./certificate');
|
||||
const now = require('./now_helper');
|
||||
|
||||
Model.knex(db);
|
||||
|
||||
class ProxyHost extends Model {
|
||||
$beforeInsert () {
|
||||
this.created_on = Model.raw('NOW()');
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
this.created_on = now();
|
||||
this.modified_on = now();
|
||||
|
||||
// Default for domain_names
|
||||
if (typeof this.domain_names === 'undefined') {
|
||||
@ -28,7 +29,7 @@ class ProxyHost extends Model {
|
||||
}
|
||||
|
||||
$beforeUpdate () {
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
this.modified_on = now();
|
||||
|
||||
// Sort domain_names
|
||||
if (typeof this.domain_names !== 'undefined') {
|
||||
|
@ -5,13 +5,14 @@ const db = require('../db');
|
||||
const Model = require('objection').Model;
|
||||
const User = require('./user');
|
||||
const Certificate = require('./certificate');
|
||||
const now = require('./now_helper');
|
||||
|
||||
Model.knex(db);
|
||||
|
||||
class RedirectionHost extends Model {
|
||||
$beforeInsert () {
|
||||
this.created_on = Model.raw('NOW()');
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
this.created_on = now();
|
||||
this.modified_on = now();
|
||||
|
||||
// Default for domain_names
|
||||
if (typeof this.domain_names === 'undefined') {
|
||||
@ -27,7 +28,7 @@ class RedirectionHost extends Model {
|
||||
}
|
||||
|
||||
$beforeUpdate () {
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
this.modified_on = now();
|
||||
|
||||
// Sort domain_names
|
||||
if (typeof this.domain_names !== 'undefined') {
|
||||
|
@ -4,13 +4,14 @@
|
||||
const db = require('../db');
|
||||
const Model = require('objection').Model;
|
||||
const User = require('./user');
|
||||
const now = require('./now_helper');
|
||||
|
||||
Model.knex(db);
|
||||
|
||||
class Stream extends Model {
|
||||
$beforeInsert () {
|
||||
this.created_on = Model.raw('NOW()');
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
this.created_on = now();
|
||||
this.modified_on = now();
|
||||
|
||||
// Default for meta
|
||||
if (typeof this.meta === 'undefined') {
|
||||
@ -19,7 +20,7 @@ class Stream extends Model {
|
||||
}
|
||||
|
||||
$beforeUpdate () {
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
this.modified_on = now();
|
||||
}
|
||||
|
||||
static get name () {
|
||||
|
@ -4,13 +4,14 @@
|
||||
const db = require('../db');
|
||||
const Model = require('objection').Model;
|
||||
const UserPermission = require('./user_permission');
|
||||
const now = require('./now_helper');
|
||||
|
||||
Model.knex(db);
|
||||
|
||||
class User extends Model {
|
||||
$beforeInsert () {
|
||||
this.created_on = Model.raw('NOW()');
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
this.created_on = now();
|
||||
this.modified_on = now();
|
||||
|
||||
// Default for roles
|
||||
if (typeof this.roles === 'undefined') {
|
||||
@ -19,7 +20,7 @@ class User extends Model {
|
||||
}
|
||||
|
||||
$beforeUpdate () {
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
this.modified_on = now();
|
||||
}
|
||||
|
||||
static get name () {
|
||||
|
@ -3,17 +3,18 @@
|
||||
|
||||
const db = require('../db');
|
||||
const Model = require('objection').Model;
|
||||
const now = require('./now_helper');
|
||||
|
||||
Model.knex(db);
|
||||
|
||||
class UserPermission extends Model {
|
||||
$beforeInsert () {
|
||||
this.created_on = Model.raw('NOW()');
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
this.created_on = now();
|
||||
this.modified_on = now();
|
||||
}
|
||||
|
||||
$beforeUpdate () {
|
||||
this.modified_on = Model.raw('NOW()');
|
||||
this.modified_on = now();
|
||||
}
|
||||
|
||||
static get name () {
|
||||
|
@ -6,28 +6,30 @@
|
||||
"dependencies": {
|
||||
"ajv": "^6.12.0",
|
||||
"batchflow": "^0.4.0",
|
||||
"bcrypt": "^4.0.1",
|
||||
"bcrypt": "^5.0.0",
|
||||
"body-parser": "^1.19.0",
|
||||
"compression": "^1.7.4",
|
||||
"config": "^3.3.1",
|
||||
"diskdb": "^0.1.17",
|
||||
"express": "^4.17.1",
|
||||
"express-fileupload": "^1.1.6",
|
||||
"express-fileupload": "^1.1.9",
|
||||
"gravatar": "^1.8.0",
|
||||
"html-entities": "^1.2.1",
|
||||
"json-schema-ref-parser": "^8.0.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"knex": "^0.20.13",
|
||||
"liquidjs": "^9.11.10",
|
||||
"lodash": "^4.17.15",
|
||||
"lodash": "^4.17.19",
|
||||
"moment": "^2.24.0",
|
||||
"mysql": "^2.18.1",
|
||||
"node-rsa": "^1.0.8",
|
||||
"nodemon": "^2.0.2",
|
||||
"objection": "^2.1.3",
|
||||
"path": "^0.12.7",
|
||||
"pg": "^7.12.1",
|
||||
"restler": "^3.4.0",
|
||||
"signale": "^1.4.0",
|
||||
"sqlite3": "^4.1.1",
|
||||
"temp-write": "^4.0.0",
|
||||
"unix-timestamp": "^0.2.0"
|
||||
},
|
||||
|
@ -58,6 +58,7 @@ router
|
||||
.post((req, res, next) => {
|
||||
apiValidator({$ref: 'endpoints/certificates#/links/1/schema'}, req.body)
|
||||
.then((payload) => {
|
||||
req.setTimeout(900000); // 15 minutes timeout
|
||||
return internalCertificate.create(res.locals.access, payload);
|
||||
})
|
||||
.then((result) => {
|
||||
@ -197,6 +198,7 @@ router
|
||||
* Renew certificate
|
||||
*/
|
||||
.post((req, res, next) => {
|
||||
req.setTimeout(900000); // 15 minutes timeout
|
||||
internalCertificate.renew(res.locals.access, {
|
||||
id: parseInt(req.params.certificate_id, 10)
|
||||
})
|
||||
|
@ -1,168 +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"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"username": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"username": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"minLength": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
"$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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -41,6 +41,24 @@
|
||||
},
|
||||
"letsencrypt_agree": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"dns_challenge": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"dns_provider": {
|
||||
"type": "string"
|
||||
},
|
||||
"dns_provider_credentials": {
|
||||
"type": "string"
|
||||
},
|
||||
"propagation_seconds": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@
|
||||
"forward_host": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 50
|
||||
"maxLength": 255
|
||||
},
|
||||
"forward_port": {
|
||||
"type": "integer",
|
||||
|
101
backend/setup.js
101
backend/setup.js
@ -5,9 +5,15 @@ const logger = require('./logger').setup;
|
||||
const userModel = require('./models/user');
|
||||
const userPermissionModel = require('./models/user_permission');
|
||||
const authModel = require('./models/auth');
|
||||
const settingModel = require('./models/setting');
|
||||
const debug_mode = process.env.NODE_ENV !== 'production' || !!process.env.DEBUG;
|
||||
|
||||
module.exports = function () {
|
||||
/**
|
||||
* Creates a new JWT RSA Keypair if not alread set on the config
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
const setupJwt = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Now go and check if the jwt gpg keys have been created and if not, create them
|
||||
if (!config.has('jwt') || !config.has('jwt.key') || !config.has('jwt.pub')) {
|
||||
@ -27,12 +33,12 @@ module.exports = function () {
|
||||
}
|
||||
|
||||
// Now create the keys and save them in the config.
|
||||
let key = new NodeRSA({b: 2048});
|
||||
let key = new NodeRSA({ b: 2048 });
|
||||
key.generateKeyPair();
|
||||
|
||||
config_data.jwt = {
|
||||
key: key.exportKey('private').toString(),
|
||||
pub: key.exportKey('public').toString()
|
||||
pub: key.exportKey('public').toString(),
|
||||
};
|
||||
|
||||
// Write config
|
||||
@ -47,7 +53,6 @@ module.exports = function () {
|
||||
process.exit(0);
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
// JWT key pair exists
|
||||
if (debug_mode) {
|
||||
@ -56,14 +61,20 @@ module.exports = function () {
|
||||
|
||||
resolve();
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
return userModel
|
||||
.query()
|
||||
.select(userModel.raw('COUNT(`id`) as `count`'))
|
||||
.where('is_deleted', 0)
|
||||
.first();
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a default admin users if one doesn't already exist in the database
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
const setupDefaultUser = () => {
|
||||
return userModel
|
||||
.query()
|
||||
.select(userModel.raw('COUNT(`id`) as `count`'))
|
||||
.where('is_deleted', 0)
|
||||
.first()
|
||||
.then((row) => {
|
||||
if (!row.count) {
|
||||
// Create a new user and set password
|
||||
@ -75,7 +86,7 @@ module.exports = function () {
|
||||
name: 'Administrator',
|
||||
nickname: 'Admin',
|
||||
avatar: '',
|
||||
roles: ['admin']
|
||||
roles: ['admin'],
|
||||
};
|
||||
|
||||
return userModel
|
||||
@ -88,28 +99,64 @@ module.exports = function () {
|
||||
user_id: user.id,
|
||||
type: 'password',
|
||||
secret: 'changeme',
|
||||
meta: {}
|
||||
meta: {},
|
||||
})
|
||||
.then(() => {
|
||||
return userPermissionModel
|
||||
.query()
|
||||
.insert({
|
||||
user_id: user.id,
|
||||
visibility: 'all',
|
||||
proxy_hosts: 'manage',
|
||||
redirection_hosts: 'manage',
|
||||
dead_hosts: 'manage',
|
||||
streams: 'manage',
|
||||
access_lists: 'manage',
|
||||
certificates: 'manage'
|
||||
});
|
||||
return userPermissionModel.query().insert({
|
||||
user_id: user.id,
|
||||
visibility: 'all',
|
||||
proxy_hosts: 'manage',
|
||||
redirection_hosts: 'manage',
|
||||
dead_hosts: 'manage',
|
||||
streams: 'manage',
|
||||
access_lists: 'manage',
|
||||
certificates: 'manage',
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
logger.info('Initial setup completed');
|
||||
logger.info('Initial admin setup completed');
|
||||
});
|
||||
} else if (debug_mode) {
|
||||
logger.debug('Admin user setup not required');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates default settings if they don't already exist in the database
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
const setupDefaultSettings = () => {
|
||||
return settingModel
|
||||
.query()
|
||||
.select(settingModel.raw('COUNT(`id`) as `count`'))
|
||||
.where({id: 'default-site'})
|
||||
.first()
|
||||
.then((row) => {
|
||||
if (!row.count) {
|
||||
settingModel
|
||||
.query()
|
||||
.insert({
|
||||
id: 'default-site',
|
||||
name: 'Default Site',
|
||||
description: 'What to show when Nginx is hit with an unknown Host',
|
||||
value: 'congratulations',
|
||||
meta: {},
|
||||
})
|
||||
.then(() => {
|
||||
logger.info('Default settings added');
|
||||
});
|
||||
}
|
||||
if (debug_mode) {
|
||||
logger.debug('Default setting setup not required');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = function () {
|
||||
return setupJwt()
|
||||
.then(setupDefaultUser)
|
||||
.then(setupDefaultSettings);
|
||||
};
|
||||
|
@ -2,6 +2,10 @@
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
{% if ipv6 -%}
|
||||
listen [::]:80;
|
||||
{% endif %}
|
||||
|
||||
server_name {{ domain_names | join: " " }};
|
||||
|
||||
access_log /data/logs/letsencrypt-requests.log standard;
|
||||
|
@ -21,11 +21,27 @@ server {
|
||||
{% if use_default_location %}
|
||||
|
||||
location / {
|
||||
{%- if access_list_id > 0 -%}
|
||||
# Access List
|
||||
|
||||
{% if access_list_id > 0 %}
|
||||
{% if access_list.items.length > 0 %}
|
||||
# Authorization
|
||||
auth_basic "Authorization required";
|
||||
auth_basic_user_file /data/access/{{ access_list_id }};
|
||||
{%- endif %}
|
||||
|
||||
{{ access_list.passauth }}
|
||||
{% endif %}
|
||||
|
||||
# Access Rules
|
||||
{% for client in access_list.clients %}
|
||||
{{- client.rule -}};
|
||||
{% endfor %}deny all;
|
||||
|
||||
# Access checks must...
|
||||
{% if access_list.satisfy %}
|
||||
{{ access_list.satisfy }};
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% include "_forced_ssl.conf" %}
|
||||
{% include "_hsts.conf" %}
|
||||
|
1308
backend/yarn.lock
1308
backend/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -16,9 +16,9 @@ ENV S6_FIX_ATTRS_HIDDEN=1
|
||||
ENV NODE_ENV=production
|
||||
|
||||
RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \
|
||||
&& rm -rf /etc/nginx \
|
||||
&& apk update \
|
||||
&& apk add python2 certbot jq \
|
||||
&& apk add python3 certbot jq \
|
||||
&& python3 -m ensurepip \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
ENV NPM_BUILD_VERSION="${BUILD_VERSION}" NPM_BUILD_COMMIT="${BUILD_COMMIT}" NPM_BUILD_DATE="${BUILD_DATE}"
|
||||
@ -34,6 +34,7 @@ EXPOSE 443
|
||||
COPY docker/rootfs /
|
||||
ADD backend /app
|
||||
ADD frontend/dist /app/frontend
|
||||
COPY global /app/global
|
||||
|
||||
WORKDIR /app
|
||||
RUN yarn install
|
||||
|
@ -6,9 +6,9 @@ ENV SUPPRESS_NO_CONFIG_WARNING=1
|
||||
ENV S6_FIX_ATTRS_HIDDEN=1
|
||||
|
||||
RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \
|
||||
&& rm -rf /etc/nginx \
|
||||
&& apk update \
|
||||
&& apk add python2 certbot jq \
|
||||
&& apk add python3 certbot jq \
|
||||
&& python3 -m ensurepip \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
# Task
|
||||
|
@ -2,14 +2,14 @@
|
||||
version: "3"
|
||||
services:
|
||||
|
||||
fullstack:
|
||||
fullstack-mysql:
|
||||
image: ${IMAGE}:ci-${BUILD_NUMBER}
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
- FORCE_COLOR=1
|
||||
volumes:
|
||||
- npm_data:/data
|
||||
- ../.jenkins/config.json:/app/config/production.json
|
||||
- ../.jenkins/config-mysql.json:/app/config/development.json
|
||||
expose:
|
||||
- 81
|
||||
- 80
|
||||
@ -17,6 +17,19 @@ services:
|
||||
depends_on:
|
||||
- db
|
||||
|
||||
fullstack-sqlite:
|
||||
image: ${IMAGE}:ci-${BUILD_NUMBER}
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
- FORCE_COLOR=1
|
||||
volumes:
|
||||
- npm_data:/data
|
||||
- ../.jenkins/config-sqlite.json:/app/config/development.json
|
||||
expose:
|
||||
- 81
|
||||
- 80
|
||||
- 443
|
||||
|
||||
db:
|
||||
image: jc21/mariadb-aria
|
||||
environment:
|
||||
@ -27,13 +40,24 @@ services:
|
||||
volumes:
|
||||
- db_data:/var/lib/mysql
|
||||
|
||||
cypress:
|
||||
cypress-mysql:
|
||||
image: ${IMAGE}-cypress:ci-${BUILD_NUMBER}
|
||||
build:
|
||||
context: ../
|
||||
dockerfile: test/cypress/Dockerfile
|
||||
environment:
|
||||
CYPRESS_baseUrl: "http://fullstack:81"
|
||||
CYPRESS_baseUrl: "http://fullstack-mysql:81"
|
||||
volumes:
|
||||
- cypress-logs:/results
|
||||
command: cypress run --browser chrome --config-file=${CYPRESS_CONFIG:-cypress/config/ci.json}
|
||||
|
||||
cypress-sqlite:
|
||||
image: ${IMAGE}-cypress:ci-${BUILD_NUMBER}
|
||||
build:
|
||||
context: ../
|
||||
dockerfile: test/cypress/Dockerfile
|
||||
environment:
|
||||
CYPRESS_baseUrl: "http://fullstack-sqlite:81"
|
||||
volumes:
|
||||
- cypress-logs:/results
|
||||
command: cypress run --browser chrome --config-file=${CYPRESS_CONFIG:-cypress/config/ci.json}
|
||||
|
@ -11,6 +11,8 @@ services:
|
||||
- 3080:80
|
||||
- 3081:81
|
||||
- 3443:443
|
||||
networks:
|
||||
- nginx_proxy_manager
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
- FORCE_COLOR=1
|
||||
@ -19,13 +21,17 @@ services:
|
||||
volumes:
|
||||
- npm_data:/data
|
||||
- le_data:/etc/letsencrypt
|
||||
- ..:/app
|
||||
- ../backend:/app
|
||||
- ../frontend:/app/frontend
|
||||
- ../global:/app/global
|
||||
depends_on:
|
||||
- db
|
||||
working_dir: /app
|
||||
|
||||
db:
|
||||
image: jc21/mariadb-aria
|
||||
networks:
|
||||
- nginx_proxy_manager
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: "npm"
|
||||
MYSQL_DATABASE: "npm"
|
||||
@ -38,6 +44,8 @@ services:
|
||||
image: 'swaggerapi/swagger-ui:latest'
|
||||
ports:
|
||||
- 3001:80
|
||||
networks:
|
||||
- nginx_proxy_manager
|
||||
environment:
|
||||
URL: "http://127.0.0.1:3081/api/schema"
|
||||
PORT: '80'
|
||||
@ -48,3 +56,6 @@ volumes:
|
||||
npm_data:
|
||||
le_data:
|
||||
db_data:
|
||||
|
||||
networks:
|
||||
nginx_proxy_manager:
|
||||
|
@ -17,6 +17,9 @@ server {
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_pass http://127.0.0.1:3000/;
|
||||
|
||||
proxy_read_timeout 15m;
|
||||
proxy_send_timeout 15m;
|
||||
}
|
||||
|
||||
location / {
|
||||
|
@ -18,6 +18,9 @@ server {
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_pass http://127.0.0.1:3000/;
|
||||
|
||||
proxy_read_timeout 15m;
|
||||
proxy_send_timeout 15m;
|
||||
}
|
||||
|
||||
location / {
|
||||
|
@ -26,12 +26,15 @@ http {
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
client_body_temp_path /tmp/nginx/body 1 2;
|
||||
keepalive_timeout 65;
|
||||
keepalive_timeout 90s;
|
||||
proxy_connect_timeout 90s;
|
||||
proxy_send_timeout 90s;
|
||||
proxy_read_timeout 90s;
|
||||
ssl_prefer_server_ciphers on;
|
||||
gzip on;
|
||||
proxy_ignore_client_abort off;
|
||||
client_max_body_size 2000m;
|
||||
server_names_hash_bucket_size 64;
|
||||
server_names_hash_bucket_size 1024;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header X-Forwarded-Scheme $scheme;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
@ -57,6 +60,9 @@ http {
|
||||
# Real IP Determination
|
||||
# Docker subnet:
|
||||
set_real_ip_from 172.0.0.0/8;
|
||||
# Local subnets:
|
||||
set_real_ip_from 10.0.0.0/8;
|
||||
set_real_ip_from 192.0.0.0/8;
|
||||
# NPM generated CDN ip ranges:
|
||||
include conf.d/include/ip_ranges.conf;
|
||||
# always put the following 2 lines after ip subnets:
|
||||
|
@ -5,7 +5,7 @@ mkdir -p /data/letsencrypt-acme-challenge
|
||||
cd /app || echo
|
||||
|
||||
if [ "$DEVELOPMENT" == "true" ]; then
|
||||
cd /app/backend || exit 1
|
||||
cd /app || exit 1
|
||||
yarn install
|
||||
node --max_old_space_size=250 --abort_on_uncaught_exception node_modules/nodemon/bin/nodemon.js
|
||||
else
|
||||
|
@ -16,5 +16,5 @@ alias h='cd ~;clear;'
|
||||
|
||||
echo -e -n '\E[1;34m'
|
||||
figlet -w 120 "NginxProxyManager"
|
||||
echo -e "\E[1;36mVersion \E[1;32m${NPM_BUILD_VERSION:-2.0.0-dev}\E[1;36m (${NPM_BUILD_COMMIT:-dev}) ${NPM_BUILD_DATE:-0000-00-00}, Nginx \E[1;32m${NGINX_VERSION:-unknown}\E[1;36m, Alpine \E[1;32m${VERSION_ID:-unknown}\E[1;36m, Kernel \E[1;32m$(uname -r)\E[0m"
|
||||
echo -e "\E[1;36mVersion \E[1;32m${NPM_BUILD_VERSION:-2.0.0-dev} (${NPM_BUILD_COMMIT:-dev}) ${NPM_BUILD_DATE:-0000-00-00}\E[1;36m, OpenResty \E[1;32m${OPENRESTY_VERSION:-unknown}\E[1;36m, Alpine \E[1;32m${VERSION_ID:-unknown}\E[1;36m, Kernel \E[1;32m$(uname -r)\E[0m"
|
||||
echo
|
||||
|
@ -47,6 +47,7 @@ module.exports = {
|
||||
["/screenshots/", "Screenshots"],
|
||||
["/setup/", "Setup Instructions"],
|
||||
["/advanced-config/", "Advanced Configuration"],
|
||||
["/faq/", "Frequently Asked Questions"],
|
||||
["/third-party/", "Third Party"]
|
||||
]
|
||||
}
|
||||
|
@ -45,7 +45,21 @@ footer: MIT Licensed | Copyright © 2016-present jc21.com
|
||||
- [Docker Install documentation](https://docs.docker.com/install/)
|
||||
- [Docker-Compose Install documentation](https://docs.docker.com/compose/install/)
|
||||
|
||||
2. Create a docker-compose.yml file similar to this:
|
||||
2. Create a config file for example
|
||||
```json
|
||||
{
|
||||
"database": {
|
||||
"engine": "mysql",
|
||||
"host": "db",
|
||||
"name": "npm",
|
||||
"user": "npm",
|
||||
"password": "npm",
|
||||
"port": 3306
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Create a docker-compose.yml file similar to this:
|
||||
|
||||
```yml
|
||||
version: '3'
|
||||
@ -71,15 +85,16 @@ services:
|
||||
- ./data/mysql:/var/lib/mysql
|
||||
```
|
||||
|
||||
3. Bring up your stack
|
||||
4. Bring up your stack
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
4. Log in to the Admin UI
|
||||
5. Log in to the Admin UI
|
||||
|
||||
When your docker container is running, connect to it on port `81` for the admin interface.
|
||||
Sometimes this can take a little bit because of the entropy of keys.
|
||||
|
||||
[http://127.0.0.1:81](http://127.0.0.1:81)
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Advanced Configuration
|
||||
|
||||
### Disabling IPv6
|
||||
## Disabling IPv6
|
||||
|
||||
On some docker hosts IPv6 may not be enabled. In these cases, the following message may be seen in the log:
|
||||
|
||||
@ -14,7 +14,7 @@ The easy fix is to add a Docker environment variable to the Nginx Proxy Manager
|
||||
```
|
||||
|
||||
|
||||
### Custom Nginx Configurations
|
||||
## Custom Nginx Configurations
|
||||
|
||||
If you are a more advanced user, you might be itching for extra Nginx customizability.
|
||||
|
||||
@ -22,18 +22,18 @@ NPM has the ability to include different custom configuration snippets in differ
|
||||
|
||||
You can add your custom configuration snippet files at `/data/nginx/custom` as follow:
|
||||
|
||||
`/data/nginx/custom/root.conf`: Included at the very end of nginx.conf
|
||||
`/data/nginx/custom/http.conf`: Included at the end of the main http block
|
||||
`/data/nginx/custom/server_proxy.conf`: Included at the end of every proxy server block
|
||||
`/data/nginx/custom/server_redirect.conf`: Included at the end of every redirection server block
|
||||
`/data/nginx/custom/server_stream.conf`: Included at the end of every stream server block
|
||||
`/data/nginx/custom/server_stream_tcp.conf`: Included at the end of every TCP stream server block
|
||||
`/data/nginx/custom/server_stream_udp.conf`: Included at the end of every UDP stream server block
|
||||
- `/data/nginx/custom/root.conf`: Included at the very end of nginx.conf
|
||||
- `/data/nginx/custom/http.conf`: Included at the end of the main http block
|
||||
- `/data/nginx/custom/server_proxy.conf`: Included at the end of every proxy server block
|
||||
- `/data/nginx/custom/server_redirect.conf`: Included at the end of every redirection server block
|
||||
- `/data/nginx/custom/server_stream.conf`: Included at the end of every stream server block
|
||||
- `/data/nginx/custom/server_stream_tcp.conf`: Included at the end of every TCP stream server block
|
||||
- `/data/nginx/custom/server_stream_udp.conf`: Included at the end of every UDP stream server block
|
||||
|
||||
Every file is optional.
|
||||
|
||||
|
||||
### X-FRAME-OPTIONS Header
|
||||
## X-FRAME-OPTIONS Header
|
||||
|
||||
You can configure the [`X-FRAME-OPTIONS`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options) header
|
||||
value by specifying it as a Docker environment variable. The default if not specified is `deny`.
|
||||
|
16
docs/faq/README.md
Normal file
16
docs/faq/README.md
Normal file
@ -0,0 +1,16 @@
|
||||
# FAQ
|
||||
|
||||
## Do I have to use Docker?
|
||||
|
||||
Yes, that's how this project is packaged.
|
||||
|
||||
This makes it easier to support the project when I have control over the version of Nginx and NodeJS
|
||||
being used. In future this could change if the backend was no longer using NodeJS and it's long list
|
||||
of dependencies.
|
||||
|
||||
|
||||
## Can I run it on a Raspberry Pi?
|
||||
|
||||
Yes! The docker image is multi-arch and is built for a variety of architectures. If yours is
|
||||
[not listed](https://hub.docker.com/r/jc21/nginx-proxy-manager/tags) please open a
|
||||
[GitHub issue](https://github.com/jc21/nginx-proxy-manager/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=).
|
@ -4,15 +4,15 @@
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@vuepress/plugin-google-analytics": "^1.4.0",
|
||||
"@vuepress/plugin-google-analytics": "^1.5.3",
|
||||
"abbrev": "^1.1.1",
|
||||
"accepts": "^1.3.7",
|
||||
"acorn": "^7.1.1",
|
||||
"agentkeepalive": "^4.1.0",
|
||||
"ajv": "^6.12.0",
|
||||
"acorn": "^7.4.0",
|
||||
"agentkeepalive": "^4.1.3",
|
||||
"ajv": "^6.12.3",
|
||||
"ajv-errors": "^1.0.1",
|
||||
"ajv-keywords": "^3.4.1",
|
||||
"algoliasearch": "^4.1.0",
|
||||
"ajv-keywords": "^3.5.2",
|
||||
"algoliasearch": "^4.3.1",
|
||||
"alphanum-sort": "^1.0.2",
|
||||
"ansi-colors": "^4.1.1",
|
||||
"ansi-escapes": "^4.3.1",
|
||||
@ -30,7 +30,7 @@
|
||||
"array-uniq": "^2.1.0",
|
||||
"array-unique": "^0.3.2",
|
||||
"asn1": "^0.2.4",
|
||||
"asn1.js": "^5.3.0",
|
||||
"asn1.js": "^5.4.1",
|
||||
"assert": "^2.0.0",
|
||||
"assert-plus": "^1.0.0",
|
||||
"assign-symbols": "^2.0.2",
|
||||
@ -40,11 +40,11 @@
|
||||
"asynckit": "^0.4.0",
|
||||
"atob": "^2.1.2",
|
||||
"autocomplete.js": "^0.37.1",
|
||||
"autoprefixer": "^9.7.6",
|
||||
"autoprefixer": "^9.8.6",
|
||||
"aws-sign2": "^0.7.0",
|
||||
"aws4": "^1.9.1",
|
||||
"aws4": "^1.10.0",
|
||||
"babel-loader": "^8.1.0",
|
||||
"babel-plugin-dynamic-import-node": "^2.3.0",
|
||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||
"babel-plugin-module-resolver": "^4.0.0",
|
||||
"balanced-match": "^1.0.0",
|
||||
"base": "^3.0.0",
|
||||
@ -52,9 +52,9 @@
|
||||
"batch": "^0.6.1",
|
||||
"bcrypt-pbkdf": "^1.0.2",
|
||||
"big.js": "^5.2.2",
|
||||
"binary-extensions": "^2.0.0",
|
||||
"binary-extensions": "^2.1.0",
|
||||
"bluebird": "^3.7.2",
|
||||
"bn.js": "^5.1.1",
|
||||
"bn.js": "^5.1.2",
|
||||
"body-parser": "^1.19.0",
|
||||
"bonjour": "^3.5.0",
|
||||
"boolbase": "^1.0.0",
|
||||
@ -65,18 +65,18 @@
|
||||
"browserify-cipher": "^1.0.1",
|
||||
"browserify-des": "^1.0.2",
|
||||
"browserify-rsa": "^4.0.1",
|
||||
"browserify-sign": "^4.0.4",
|
||||
"browserify-sign": "^4.2.1",
|
||||
"browserify-zlib": "^0.2.0",
|
||||
"browserslist": "^4.11.1",
|
||||
"buffer": "^5.5.0",
|
||||
"browserslist": "^4.13.0",
|
||||
"buffer": "^5.6.0",
|
||||
"buffer-from": "^1.1.1",
|
||||
"buffer-indexof": "^1.1.1",
|
||||
"buffer-json": "^2.0.0",
|
||||
"buffer-xor": "^2.0.2",
|
||||
"builtin-status-codes": "^3.0.0",
|
||||
"bytes": "^3.1.0",
|
||||
"cac": "^6.5.8",
|
||||
"cacache": "^15.0.0",
|
||||
"cac": "^6.6.1",
|
||||
"cacache": "^15.0.5",
|
||||
"cache-base": "^4.0.0",
|
||||
"cache-loader": "^4.1.0",
|
||||
"call-me-maybe": "^1.0.1",
|
||||
@ -84,12 +84,12 @@
|
||||
"caller-path": "^3.0.0",
|
||||
"callsites": "^3.1.0",
|
||||
"camel-case": "^4.1.1",
|
||||
"camelcase": "^5.3.1",
|
||||
"camelcase": "^6.0.0",
|
||||
"caniuse-api": "^3.0.0",
|
||||
"caniuse-lite": "^1.0.30001039",
|
||||
"caniuse-lite": "^1.0.30001111",
|
||||
"caseless": "^0.12.0",
|
||||
"chalk": "^4.0.0",
|
||||
"chokidar": "^3.3.1",
|
||||
"chalk": "^4.1.0",
|
||||
"chokidar": "^3.4.1",
|
||||
"chownr": "^2.0.0",
|
||||
"chrome-trace-event": "^1.0.2",
|
||||
"ci-info": "^2.0.0",
|
||||
@ -106,7 +106,7 @@
|
||||
"color-name": "^1.1.4",
|
||||
"color-string": "^1.5.3",
|
||||
"combined-stream": "^1.0.8",
|
||||
"commander": "^5.0.0",
|
||||
"commander": "^6.0.0",
|
||||
"commondir": "^1.0.1",
|
||||
"component-emitter": "^1.3.0",
|
||||
"compressible": "^2.0.18",
|
||||
@ -114,36 +114,36 @@
|
||||
"concat-map": "^0.0.1",
|
||||
"concat-stream": "^2.0.0",
|
||||
"connect-history-api-fallback": "^1.6.0",
|
||||
"consola": "^2.11.3",
|
||||
"consola": "^2.15.0",
|
||||
"console-browserify": "^1.2.0",
|
||||
"consolidate": "^0.15.1",
|
||||
"constants-browserify": "^1.0.0",
|
||||
"content-disposition": "^0.5.3",
|
||||
"content-type": "^1.0.4",
|
||||
"convert-source-map": "^1.7.0",
|
||||
"cookie": "^0.4.0",
|
||||
"cookie": "^0.4.1",
|
||||
"cookie-signature": "^1.1.0",
|
||||
"copy-concurrently": "^1.0.5",
|
||||
"copy-descriptor": "^0.1.1",
|
||||
"copy-webpack-plugin": "^5.1.1",
|
||||
"core-js": "^3.6.4",
|
||||
"copy-webpack-plugin": "^6.0.3",
|
||||
"core-js": "^3.6.5",
|
||||
"core-util-is": "^1.0.2",
|
||||
"cosmiconfig": "^6.0.0",
|
||||
"create-ecdh": "^4.0.3",
|
||||
"cosmiconfig": "^7.0.0",
|
||||
"create-ecdh": "^4.0.4",
|
||||
"create-hash": "^1.2.0",
|
||||
"create-hmac": "^1.1.7",
|
||||
"cross-spawn": "^7.0.2",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"css": "^2.2.4",
|
||||
"css": "^3.0.0",
|
||||
"css-color-names": "^1.0.1",
|
||||
"css-declaration-sorter": "^5.1.2",
|
||||
"css-loader": "^3.5.0",
|
||||
"css-loader": "^4.2.0",
|
||||
"css-parse": "^2.0.0",
|
||||
"css-select": "^2.1.0",
|
||||
"css-select-base-adapter": "^0.1.1",
|
||||
"css-tree": "^1.0.0-alpha.39",
|
||||
"css-unit-converter": "^1.1.1",
|
||||
"css-what": "^3.2.1",
|
||||
"css-unit-converter": "^1.1.2",
|
||||
"css-what": "^3.3.0",
|
||||
"cssesc": "^3.0.0",
|
||||
"cssnano": "^4.1.10",
|
||||
"cssnano-preset-default": "^4.0.7",
|
||||
@ -158,9 +158,9 @@
|
||||
"debug": "^4.1.1",
|
||||
"decamelize": "^4.0.0",
|
||||
"decode-uri-component": "^0.2.0",
|
||||
"deep-equal": "^2.0.2",
|
||||
"deep-equal": "^2.0.3",
|
||||
"deepmerge": "^4.2.2",
|
||||
"default-gateway": "^6.0.0",
|
||||
"default-gateway": "^6.0.1",
|
||||
"define-properties": "^1.1.3",
|
||||
"define-property": "^2.0.2",
|
||||
"del": "^5.1.0",
|
||||
@ -178,52 +178,52 @@
|
||||
"dns-txt": "^2.0.2",
|
||||
"docsearch.js": "^2.6.3",
|
||||
"dom-converter": "^0.2.0",
|
||||
"dom-serializer": "^0.2.2",
|
||||
"dom-serializer": "^1.0.1",
|
||||
"dom-walk": "^0.1.2",
|
||||
"domain-browser": "^4.0.0",
|
||||
"domain-browser": "^4.16.0",
|
||||
"domelementtype": "^2.0.1",
|
||||
"domhandler": "^3.0.0",
|
||||
"domutils": "^2.0.0",
|
||||
"domutils": "^2.1.0",
|
||||
"dot-prop": "^5.2.0",
|
||||
"duplexify": "^4.1.1",
|
||||
"ecc-jsbn": "^0.2.0",
|
||||
"ee-first": "^1.1.1",
|
||||
"electron-to-chromium": "^1.3.397",
|
||||
"elliptic": "^6.5.2",
|
||||
"emoji-regex": "^8.0.0",
|
||||
"electron-to-chromium": "^1.3.522",
|
||||
"elliptic": "^6.5.3",
|
||||
"emoji-regex": "^9.0.0",
|
||||
"emojis-list": "^3.0.0",
|
||||
"encodeurl": "^1.0.2",
|
||||
"end-of-stream": "^1.4.4",
|
||||
"enhanced-resolve": "^4.1.1",
|
||||
"entities": "^2.0.0",
|
||||
"enhanced-resolve": "^4.3.0",
|
||||
"entities": "^2.0.3",
|
||||
"envify": "^4.1.0",
|
||||
"envinfo": "^7.5.0",
|
||||
"envinfo": "^7.7.2",
|
||||
"errno": "^0.1.7",
|
||||
"error-ex": "^1.3.2",
|
||||
"es-abstract": "^1.17.5",
|
||||
"es-abstract": "^1.17.6",
|
||||
"es-to-primitive": "^1.2.1",
|
||||
"es6-promise": "^4.2.8",
|
||||
"escape-html": "^1.0.3",
|
||||
"escape-string-regexp": "^2.0.0",
|
||||
"eslint-scope": "^5.0.0",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"eslint-scope": "^5.1.0",
|
||||
"esprima": "^4.0.1",
|
||||
"esrecurse": "^4.2.1",
|
||||
"estraverse": "^5.0.0",
|
||||
"estraverse": "^5.2.0",
|
||||
"esutils": "^2.0.3",
|
||||
"etag": "^1.8.1",
|
||||
"eventemitter3": "^4.0.0",
|
||||
"events": "^3.1.0",
|
||||
"eventemitter3": "^4.0.4",
|
||||
"events": "^3.2.0",
|
||||
"eventsource": "^1.0.7",
|
||||
"evp_bytestokey": "^1.0.3",
|
||||
"execa": "^4.0.0",
|
||||
"execa": "^4.0.3",
|
||||
"expand-brackets": "^4.0.0",
|
||||
"express": "^4.17.1",
|
||||
"extend": "^3.0.2",
|
||||
"extend-shallow": "^3.0.2",
|
||||
"extglob": "^3.0.0",
|
||||
"extsprintf": "^1.4.0",
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-glob": "^3.2.2",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-glob": "^3.2.4",
|
||||
"fast-json-stable-stringify": "^2.1.0",
|
||||
"faye-websocket": "^0.11.3",
|
||||
"figgy-pudding": "^3.5.2",
|
||||
@ -235,7 +235,7 @@
|
||||
"find-cache-dir": "^3.3.1",
|
||||
"find-up": "^4.1.0",
|
||||
"flush-write-stream": "^2.0.0",
|
||||
"follow-redirects": "^1.11.0",
|
||||
"follow-redirects": "^1.12.1",
|
||||
"for-in": "^1.0.2",
|
||||
"foreach": "^2.0.5",
|
||||
"forever-agent": "^0.6.1",
|
||||
@ -244,7 +244,7 @@
|
||||
"fragment-cache": "^0.2.1",
|
||||
"fresh": "^0.5.2",
|
||||
"from2": "^2.3.0",
|
||||
"fs-extra": "^9.0.0",
|
||||
"fs-extra": "^9.0.1",
|
||||
"fs-write-stream-atomic": "^1.0.10",
|
||||
"fs.realpath": "^1.0.0",
|
||||
"function-bind": "^1.1.1",
|
||||
@ -257,21 +257,21 @@
|
||||
"glob-parent": "^5.1.1",
|
||||
"glob-to-regexp": "^0.4.1",
|
||||
"global": "^4.4.0",
|
||||
"globals": "^12.4.0",
|
||||
"globby": "^11.0.0",
|
||||
"globals": "^13.1.0",
|
||||
"globby": "^11.0.1",
|
||||
"good-listener": "^1.2.2",
|
||||
"graceful-fs": "^4.2.3",
|
||||
"graceful-fs": "^4.2.4",
|
||||
"gray-matter": "^4.0.2",
|
||||
"handle-thing": "^2.0.1",
|
||||
"har-schema": "^2.0.0",
|
||||
"har-validator": "^5.1.3",
|
||||
"har-validator": "^5.1.5",
|
||||
"has": "^1.0.3",
|
||||
"has-ansi": "^4.0.0",
|
||||
"has-flag": "^4.0.0",
|
||||
"has-symbols": "^1.0.1",
|
||||
"has-value": "^2.0.2",
|
||||
"has-values": "^2.0.1",
|
||||
"hash-base": "^3.0.4",
|
||||
"hash-base": "^3.1.0",
|
||||
"hash-sum": "^2.0.0",
|
||||
"hash.js": "^1.1.7",
|
||||
"he": "^1.2.0",
|
||||
@ -282,24 +282,24 @@
|
||||
"hsl-regex": "^1.0.0",
|
||||
"hsla-regex": "^1.0.0",
|
||||
"html-comment-regex": "^1.1.2",
|
||||
"html-entities": "^1.2.1",
|
||||
"html-entities": "^1.3.1",
|
||||
"html-minifier": "^4.0.0",
|
||||
"html-tags": "^3.1.0",
|
||||
"htmlparser2": "^4.1.0",
|
||||
"http-deceiver": "^1.2.7",
|
||||
"http-errors": "^1.7.3",
|
||||
"http-errors": "^1.8.0",
|
||||
"http-parser-js": "^0.5.2",
|
||||
"http-proxy": "^1.18.0",
|
||||
"http-proxy-middleware": "^1.0.3",
|
||||
"http-proxy": "^1.18.1",
|
||||
"http-proxy-middleware": "^1.0.5",
|
||||
"http-signature": "^1.3.4",
|
||||
"https-browserify": "^1.0.0",
|
||||
"iconv-lite": "^0.5.1",
|
||||
"iconv-lite": "^0.6.2",
|
||||
"icss-replace-symbols": "^1.1.0",
|
||||
"icss-utils": "^4.1.1",
|
||||
"ieee754": "^1.1.13",
|
||||
"iferr": "^1.0.2",
|
||||
"ignore": "^5.1.4",
|
||||
"immediate": "^3.2.3",
|
||||
"ignore": "^5.1.8",
|
||||
"immediate": "^3.3.0",
|
||||
"import-cwd": "^3.0.0",
|
||||
"import-fresh": "^3.2.1",
|
||||
"import-from": "^3.0.0",
|
||||
@ -309,9 +309,9 @@
|
||||
"infer-owner": "^1.0.4",
|
||||
"inflight": "^1.0.6",
|
||||
"inherits": "^2.0.4",
|
||||
"internal-ip": "^6.0.0",
|
||||
"internal-ip": "^6.1.0",
|
||||
"invariant": "^2.2.4",
|
||||
"invert-kv": "^3.0.0",
|
||||
"invert-kv": "^3.0.1",
|
||||
"ip": "^1.1.5",
|
||||
"ip-regex": "^4.1.0",
|
||||
"ipaddr.js": "^1.9.1",
|
||||
@ -321,7 +321,7 @@
|
||||
"is-arrayish": "^0.3.2",
|
||||
"is-binary-path": "^2.1.0",
|
||||
"is-buffer": "^2.0.4",
|
||||
"is-callable": "^1.1.5",
|
||||
"is-callable": "^1.2.0",
|
||||
"is-color-stop": "^1.1.0",
|
||||
"is-data-descriptor": "^2.0.0",
|
||||
"is-date-object": "^1.0.2",
|
||||
@ -337,25 +337,25 @@
|
||||
"is-path-in-cwd": "^3.0.0",
|
||||
"is-path-inside": "^3.0.2",
|
||||
"is-plain-obj": "^2.1.0",
|
||||
"is-plain-object": "^3.0.0",
|
||||
"is-regex": "^1.0.5",
|
||||
"is-plain-object": "^4.1.1",
|
||||
"is-regex": "^1.1.1",
|
||||
"is-resolvable": "^1.1.0",
|
||||
"is-stream": "^2.0.0",
|
||||
"is-svg": "^4.2.1",
|
||||
"is-symbol": "^1.0.3",
|
||||
"is-typedarray": "^1.0.0",
|
||||
"is-windows": "^1.0.2",
|
||||
"is-wsl": "^2.1.1",
|
||||
"is-wsl": "^2.2.0",
|
||||
"isarray": "^2.0.5",
|
||||
"isexe": "^2.0.0",
|
||||
"isobject": "^4.0.0",
|
||||
"isstream": "^0.1.2",
|
||||
"javascript-stringify": "^2.0.1",
|
||||
"js-levenshtein": "^1.1.6",
|
||||
"js-tokens": "^5.0.0",
|
||||
"js-yaml": "^3.13.1",
|
||||
"js-tokens": "^6.0.0",
|
||||
"js-yaml": "^3.14.0",
|
||||
"jsbn": "^1.1.0",
|
||||
"jsesc": "^2.5.2",
|
||||
"jsesc": "^3.0.1",
|
||||
"json-parse-better-errors": "^1.0.2",
|
||||
"json-schema": "^0.2.5",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
@ -368,12 +368,12 @@
|
||||
"kind-of": "^6.0.3",
|
||||
"last-call-webpack-plugin": "^3.0.0",
|
||||
"lcid": "^3.1.1",
|
||||
"linkify-it": "^2.2.0",
|
||||
"linkify-it": "^3.0.2",
|
||||
"load-script": "^1.0.0",
|
||||
"loader-runner": "^3.1.0",
|
||||
"loader-runner": "^4.0.0",
|
||||
"loader-utils": "^2.0.0",
|
||||
"locate-path": "^5.0.0",
|
||||
"lodash": "^4.17.15",
|
||||
"lodash": "^4.17.19",
|
||||
"lodash._reinterpolate": "^3.0.0",
|
||||
"lodash.chunk": "^4.2.0",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
@ -385,37 +385,37 @@
|
||||
"lodash.template": "^4.5.0",
|
||||
"lodash.templatesettings": "^4.2.0",
|
||||
"lodash.uniq": "^4.5.0",
|
||||
"loglevel": "^1.6.7",
|
||||
"loglevel": "^1.6.8",
|
||||
"loose-envify": "^1.4.0",
|
||||
"lower-case": "^2.0.1",
|
||||
"lru-cache": "^5.1.1",
|
||||
"make-dir": "^3.0.2",
|
||||
"lru-cache": "^6.0.0",
|
||||
"make-dir": "^3.1.0",
|
||||
"mamacro": "^0.0.7",
|
||||
"map-age-cleaner": "^0.1.3",
|
||||
"map-cache": "^0.2.2",
|
||||
"map-visit": "^1.0.0",
|
||||
"markdown-it": "^10.0.0",
|
||||
"markdown-it-anchor": "^5.2.7",
|
||||
"markdown-it": "^11.0.0",
|
||||
"markdown-it-anchor": "^5.3.0",
|
||||
"markdown-it-chain": "^1.3.0",
|
||||
"markdown-it-container": "^2.0.0",
|
||||
"markdown-it-container": "^3.0.0",
|
||||
"markdown-it-emoji": "^1.4.0",
|
||||
"markdown-it-table-of-contents": "^0.4.4",
|
||||
"md5.js": "^1.3.5",
|
||||
"mdn-data": "^2.0.8",
|
||||
"mdn-data": "^2.0.11",
|
||||
"mdurl": "^1.0.1",
|
||||
"media-typer": "^1.1.0",
|
||||
"mem": "^6.0.1",
|
||||
"mem": "^6.1.0",
|
||||
"memory-fs": "^0.5.0",
|
||||
"merge-descriptors": "^1.0.1",
|
||||
"merge-source-map": "^1.1.0",
|
||||
"merge2": "^1.3.0",
|
||||
"merge2": "^1.4.1",
|
||||
"methods": "^1.1.2",
|
||||
"micromatch": "^4.0.2",
|
||||
"miller-rabin": "^4.0.1",
|
||||
"mime": "^2.4.4",
|
||||
"mime-db": "^1.43.0",
|
||||
"mime-types": "^2.1.26",
|
||||
"mimic-fn": "^3.0.0",
|
||||
"mime": "^2.4.6",
|
||||
"mime-db": "^1.44.0",
|
||||
"mime-types": "^2.1.27",
|
||||
"mimic-fn": "^3.1.0",
|
||||
"min-document": "^2.19.0",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"minimalistic-assert": "^1.0.1",
|
||||
@ -431,16 +431,16 @@
|
||||
"multicast-dns-service-types": "^1.1.0",
|
||||
"nanomatch": "^1.2.13",
|
||||
"negotiator": "^0.6.2",
|
||||
"neo-async": "^2.6.1",
|
||||
"neo-async": "^2.6.2",
|
||||
"nice-try": "^2.0.1",
|
||||
"no-case": "^3.0.3",
|
||||
"node-forge": "^0.9.1",
|
||||
"node-forge": "^0.10.0",
|
||||
"node-libs-browser": "^2.2.1",
|
||||
"node-releases": "^1.1.53",
|
||||
"node-releases": "^1.1.60",
|
||||
"nopt": "^4.0.3",
|
||||
"normalize-path": "^3.0.0",
|
||||
"normalize-range": "^0.1.2",
|
||||
"normalize-url": "^5.0.0",
|
||||
"normalize-url": "^5.1.0",
|
||||
"npm-run-path": "^4.0.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"nth-check": "^1.0.2",
|
||||
@ -449,8 +449,8 @@
|
||||
"oauth-sign": "^0.9.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"object-copy": "^1.0.0",
|
||||
"object-inspect": "^1.7.0",
|
||||
"object-is": "^1.0.2",
|
||||
"object-inspect": "^1.8.0",
|
||||
"object-is": "^1.1.2",
|
||||
"object-keys": "^1.1.1",
|
||||
"object-visit": "^1.0.1",
|
||||
"object.assign": "^4.1.0",
|
||||
@ -461,7 +461,7 @@
|
||||
"on-finished": "^2.3.0",
|
||||
"on-headers": "^1.0.2",
|
||||
"once": "^1.4.0",
|
||||
"opencollective-postinstall": "^2.0.2",
|
||||
"opencollective-postinstall": "^2.0.3",
|
||||
"opn": "^6.0.0",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
||||
"original": "^1.0.2",
|
||||
@ -470,7 +470,7 @@
|
||||
"p-defer": "^3.0.0",
|
||||
"p-finally": "^2.0.1",
|
||||
"p-is-promise": "^3.0.0",
|
||||
"p-limit": "^2.3.0",
|
||||
"p-limit": "^3.0.2",
|
||||
"p-locate": "^4.1.0",
|
||||
"p-map": "^4.0.0",
|
||||
"p-retry": "^4.2.0",
|
||||
@ -479,7 +479,7 @@
|
||||
"parallel-transform": "^1.2.0",
|
||||
"param-case": "^3.0.3",
|
||||
"parse-asn1": "^5.1.5",
|
||||
"parse-json": "^5.0.0",
|
||||
"parse-json": "^5.0.1",
|
||||
"parseurl": "^1.3.3",
|
||||
"pascalcase": "^1.0.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
@ -491,16 +491,16 @@
|
||||
"path-parse": "^1.0.6",
|
||||
"path-to-regexp": "^6.1.0",
|
||||
"path-type": "^4.0.0",
|
||||
"pbkdf2": "^3.0.17",
|
||||
"pbkdf2": "^3.1.1",
|
||||
"performance-now": "^2.1.0",
|
||||
"pify": "^5.0.0",
|
||||
"pinkie": "^2.0.4",
|
||||
"pinkie-promise": "^2.0.1",
|
||||
"pkg-dir": "^4.2.0",
|
||||
"pkg-up": "^3.1.0",
|
||||
"portfinder": "^1.0.25",
|
||||
"portfinder": "^1.0.28",
|
||||
"posix-character-classes": "^1.0.0",
|
||||
"postcss": "^7.0.27",
|
||||
"postcss": "^7.0.32",
|
||||
"postcss-calc": "^7.0.2",
|
||||
"postcss-colormin": "^4.0.3",
|
||||
"postcss-convert-values": "^4.0.1",
|
||||
@ -517,7 +517,7 @@
|
||||
"postcss-minify-params": "^4.0.2",
|
||||
"postcss-minify-selectors": "^4.0.2",
|
||||
"postcss-modules-extract-imports": "^2.0.0",
|
||||
"postcss-modules-local-by-default": "^3.0.2",
|
||||
"postcss-modules-local-by-default": "^3.0.3",
|
||||
"postcss-modules-scope": "^2.2.0",
|
||||
"postcss-modules-values": "^3.0.0",
|
||||
"postcss-normalize-charset": "^4.0.1",
|
||||
@ -536,9 +536,9 @@
|
||||
"postcss-selector-parser": "^6.0.2",
|
||||
"postcss-svgo": "^4.0.2",
|
||||
"postcss-unique-selectors": "^4.0.1",
|
||||
"postcss-value-parser": "^4.0.3",
|
||||
"postcss-value-parser": "^4.1.0",
|
||||
"prepend-http": "^3.0.1",
|
||||
"prettier": "^2.0.4",
|
||||
"prettier": "^2.0.5",
|
||||
"pretty-error": "^2.1.1",
|
||||
"pretty-time": "^1.1.0",
|
||||
"prismjs": "^1.20.0",
|
||||
@ -555,8 +555,8 @@
|
||||
"pumpify": "^2.0.1",
|
||||
"punycode": "^2.1.1",
|
||||
"q": "^1.5.1",
|
||||
"qs": "^6.9.3",
|
||||
"query-string": "^6.12.0",
|
||||
"qs": "^6.9.4",
|
||||
"query-string": "^6.13.1",
|
||||
"querystring": "^0.2.0",
|
||||
"querystring-es3": "^0.2.1",
|
||||
"querystringify": "^2.1.1",
|
||||
@ -567,14 +567,14 @@
|
||||
"readable-stream": "^3.6.0",
|
||||
"readdirp": "^3.4.0",
|
||||
"reduce": "^1.0.2",
|
||||
"regenerate": "^1.4.0",
|
||||
"regenerate": "^1.4.1",
|
||||
"regenerate-unicode-properties": "^8.2.0",
|
||||
"regenerator-runtime": "^0.13.5",
|
||||
"regenerator-transform": "^0.14.4",
|
||||
"regenerator-runtime": "^0.13.7",
|
||||
"regenerator-transform": "^0.14.5",
|
||||
"regex-not": "^1.0.2",
|
||||
"regexp.prototype.flags": "^1.3.0",
|
||||
"regexpu-core": "^4.7.0",
|
||||
"regjsgen": "^0.5.1",
|
||||
"regjsgen": "^0.5.2",
|
||||
"regjsparser": "^0.6.4",
|
||||
"relateurl": "^0.2.7",
|
||||
"remove-trailing-separator": "^1.1.0",
|
||||
@ -586,7 +586,7 @@
|
||||
"require-main-filename": "^2.0.0",
|
||||
"requires-port": "^1.0.0",
|
||||
"reselect": "^4.0.0",
|
||||
"resolve": "^1.15.1",
|
||||
"resolve": "^1.17.0",
|
||||
"resolve-cwd": "^3.0.0",
|
||||
"resolve-from": "^5.0.0",
|
||||
"resolve-url": "^0.2.1",
|
||||
@ -597,18 +597,18 @@
|
||||
"rimraf": "^3.0.2",
|
||||
"ripemd160": "^2.0.2",
|
||||
"run-queue": "^2.0.1",
|
||||
"safe-buffer": "^5.2.0",
|
||||
"safe-buffer": "^5.2.1",
|
||||
"safe-regex": "^2.1.1",
|
||||
"safer-buffer": "^2.1.2",
|
||||
"sax": "^1.2.4",
|
||||
"schema-utils": "^2.6.5",
|
||||
"schema-utils": "^2.7.0",
|
||||
"section-matter": "^1.0.0",
|
||||
"select": "^1.1.2",
|
||||
"select-hose": "^2.0.0",
|
||||
"selfsigned": "^1.10.7",
|
||||
"semver": "^7.2.1",
|
||||
"semver": "^7.3.2",
|
||||
"send": "^0.17.1",
|
||||
"serialize-javascript": "^3.0.0",
|
||||
"serialize-javascript": "^4.0.0",
|
||||
"serve-index": "^1.9.1",
|
||||
"serve-static": "^1.14.1",
|
||||
"set-blocking": "^2.0.0",
|
||||
@ -620,19 +620,19 @@
|
||||
"shebang-regex": "^3.0.0",
|
||||
"signal-exit": "^3.0.3",
|
||||
"simple-swizzle": "^0.2.2",
|
||||
"sitemap": "^6.1.0",
|
||||
"sitemap": "^6.2.0",
|
||||
"slash": "^3.0.0",
|
||||
"smoothscroll-polyfill": "^0.4.4",
|
||||
"snapdragon": "^0.12.0",
|
||||
"snapdragon-node": "^3.0.0",
|
||||
"snapdragon-util": "^5.0.1",
|
||||
"sockjs": "^0.3.20",
|
||||
"sockjs-client": "^1.4.0",
|
||||
"sockjs": "^0.3.21",
|
||||
"sockjs-client": "^1.5.0",
|
||||
"sort-keys": "^4.0.0",
|
||||
"source-list-map": "^2.0.1",
|
||||
"source-map": "^0.7.3",
|
||||
"source-map-resolve": "^0.6.0",
|
||||
"source-map-support": "^0.5.16",
|
||||
"source-map-support": "^0.5.19",
|
||||
"source-map-url": "^0.4.0",
|
||||
"spdy": "^4.0.2",
|
||||
"spdy-transport": "^3.0.0",
|
||||
@ -641,13 +641,13 @@
|
||||
"sshpk": "^1.16.1",
|
||||
"ssri": "^8.0.0",
|
||||
"stable": "^0.1.8",
|
||||
"stack-utils": "^2.0.1",
|
||||
"stack-utils": "^2.0.2",
|
||||
"static-extend": "^0.1.2",
|
||||
"statuses": "^1.5.0",
|
||||
"statuses": "^2.0.0",
|
||||
"std-env": "^2.2.1",
|
||||
"stream-browserify": "^2.0.2",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"stream-each": "^1.2.3",
|
||||
"stream-http": "^3.1.0",
|
||||
"stream-http": "^3.1.1",
|
||||
"stream-shift": "^1.0.1",
|
||||
"strict-uri-encode": "^2.0.0",
|
||||
"string-width": "^4.2.0",
|
||||
@ -658,17 +658,17 @@
|
||||
"strip-bom-string": "^1.0.0",
|
||||
"strip-eof": "^2.0.0",
|
||||
"stylehacks": "^4.0.3",
|
||||
"stylus": "^0.54.7",
|
||||
"stylus": "^0.54.8",
|
||||
"stylus-loader": "^3.0.2",
|
||||
"supports-color": "^7.1.0",
|
||||
"svg-tags": "^1.0.0",
|
||||
"svgo": "^1.3.2",
|
||||
"tapable": "^1.1.3",
|
||||
"terser": "^4.6.10",
|
||||
"terser-webpack-plugin": "^2.3.5",
|
||||
"terser": "^5.0.0",
|
||||
"terser-webpack-plugin": "^4.0.0",
|
||||
"text-table": "^0.2.0",
|
||||
"through": "^2.3.8",
|
||||
"through2": "^3.0.1",
|
||||
"through2": "^4.0.2",
|
||||
"thunky": "^1.1.0",
|
||||
"timers-browserify": "^2.0.11",
|
||||
"timsort": "^0.3.0",
|
||||
@ -684,15 +684,15 @@
|
||||
"toposort": "^2.0.2",
|
||||
"tough-cookie": "^4.0.0",
|
||||
"tr46": "^2.0.2",
|
||||
"tslib": "^1.11.1",
|
||||
"tslib": "^2.0.0",
|
||||
"tty-browserify": "^0.0.1",
|
||||
"tunnel-agent": "^0.6.0",
|
||||
"tweetnacl": "^1.0.3",
|
||||
"type-fest": "^0.13.0",
|
||||
"type-fest": "^0.16.0",
|
||||
"type-is": "^1.6.18",
|
||||
"typedarray": "^0.0.6",
|
||||
"uc.micro": "^1.0.6",
|
||||
"uglify-js": "^3.8.1",
|
||||
"uglify-js": "^3.10.1",
|
||||
"unicode-canonical-property-names-ecmascript": "^1.0.4",
|
||||
"unicode-match-property-ecmascript": "^1.0.4",
|
||||
"unicode-match-property-value-ecmascript": "^1.2.0",
|
||||
@ -702,7 +702,7 @@
|
||||
"uniqs": "^2.0.0",
|
||||
"unique-filename": "^1.1.1",
|
||||
"unique-slug": "^2.0.2",
|
||||
"universalify": "^1.0.0",
|
||||
"universalify": "^2.0.0",
|
||||
"unpipe": "^1.0.0",
|
||||
"unquote": "^1.1.1",
|
||||
"unset-value": "^1.0.0",
|
||||
@ -711,60 +711,60 @@
|
||||
"uri-js": "^4.2.2",
|
||||
"urix": "^0.1.0",
|
||||
"url": "^0.11.0",
|
||||
"url-loader": "^4.0.0",
|
||||
"url-loader": "^4.1.0",
|
||||
"url-parse": "^1.4.7",
|
||||
"use": "^3.1.1",
|
||||
"util": "^0.12.2",
|
||||
"util": "^0.12.3",
|
||||
"util-deprecate": "^1.0.2",
|
||||
"util.promisify": "^1.0.1",
|
||||
"utila": "^0.4.0",
|
||||
"utils-merge": "^1.0.1",
|
||||
"uuid": "^7.0.3",
|
||||
"uuid": "^8.3.0",
|
||||
"vary": "^1.1.2",
|
||||
"vendors": "^1.0.4",
|
||||
"verror": "^1.10.0",
|
||||
"vm-browserify": "^1.1.2",
|
||||
"vue": "^2.6.11",
|
||||
"vue-hot-reload-api": "^2.3.4",
|
||||
"vue-loader": "^15.9.1",
|
||||
"vue-router": "^3.1.6",
|
||||
"vue-loader": "^15.9.3",
|
||||
"vue-router": "^3.4.0",
|
||||
"vue-server-renderer": "^2.6.11",
|
||||
"vue-style-loader": "^4.1.2",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"vue-template-es2015-compiler": "^1.9.1",
|
||||
"vuepress": "^1.4.0",
|
||||
"vuepress": "^1.5.3",
|
||||
"vuepress-html-webpack-plugin": "^3.2.0",
|
||||
"vuepress-plugin-container": "^2.1.2",
|
||||
"vuepress-plugin-container": "^2.1.4",
|
||||
"vuepress-plugin-sitemap": "^2.3.1",
|
||||
"vuepress-plugin-smooth-scroll": "^0.0.9",
|
||||
"vuepress-plugin-zooming": "^1.1.7",
|
||||
"watchpack": "^1.6.1",
|
||||
"watchpack": "^1.7.4",
|
||||
"wbuf": "^1.7.3",
|
||||
"webidl-conversions": "^6.0.0",
|
||||
"webpack": "^4.42.1",
|
||||
"webpack-chain": "^6.4.0",
|
||||
"webidl-conversions": "^6.1.0",
|
||||
"webpack": "^4.44.1",
|
||||
"webpack-chain": "^6.5.1",
|
||||
"webpack-dev-middleware": "^3.7.2",
|
||||
"webpack-dev-server": "^3.10.3",
|
||||
"webpack-dev-server": "^3.11.0",
|
||||
"webpack-log": "^3.0.1",
|
||||
"webpack-merge": "^4.2.2",
|
||||
"webpack-merge": "^5.1.1",
|
||||
"webpack-sources": "^1.4.3",
|
||||
"webpackbar": "^4.0.0",
|
||||
"websocket-driver": "^0.7.3",
|
||||
"websocket-extensions": "^0.1.3",
|
||||
"whatwg-url": "^8.0.0",
|
||||
"websocket-driver": "^0.7.4",
|
||||
"websocket-extensions": "^0.1.4",
|
||||
"whatwg-url": "^8.1.0",
|
||||
"when": "^3.7.8",
|
||||
"which": "^2.0.2",
|
||||
"which-module": "^2.0.0",
|
||||
"worker-farm": "^1.7.0",
|
||||
"wrap-ansi": "^6.2.0",
|
||||
"wrap-ansi": "^7.0.0",
|
||||
"wrappy": "^1.0.2",
|
||||
"ws": "^7.2.3",
|
||||
"xmlbuilder": "^15.1.0",
|
||||
"ws": "^7.3.1",
|
||||
"xmlbuilder": "^15.1.1",
|
||||
"xtend": "^4.0.2",
|
||||
"y18n": "^4.0.0",
|
||||
"yallist": "^4.0.0",
|
||||
"yargs": "^15.3.1",
|
||||
"yargs-parser": "^18.1.2",
|
||||
"yargs": "^15.4.1",
|
||||
"yargs-parser": "^18.1.3",
|
||||
"zepto": "^1.2.0"
|
||||
},
|
||||
"devDependencies": {},
|
||||
|
@ -23,15 +23,31 @@ Here's an example configuration for `mysql` (or mariadb) that is compatible with
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively if you would like to use a Sqlite database file:
|
||||
|
||||
```json
|
||||
{
|
||||
"database": {
|
||||
"engine": "knex-native",
|
||||
"knex": {
|
||||
"client": "sqlite3",
|
||||
"connection": {
|
||||
"filename": "/data/database.sqlite"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Once you've created your configuration file it's easy to mount it in the docker container.
|
||||
|
||||
**Note:** After the first run of the application, the config file will be altered to include generated encryption keys unique to your installation. These keys
|
||||
affect the login and session management of the application. If these keys change for any reason, all users will be logged out.
|
||||
|
||||
|
||||
### Database
|
||||
### MySQL Database
|
||||
|
||||
This app doesn't come with a database, you have to provide one yourself. Currently only `mysql/mariadb` is supported for the minimum versions:
|
||||
If you opt for the MySQL configuration you will have to provide the database server yourself. You can also use MariaDB. Here are the minimum supported versions:
|
||||
|
||||
- MySQL v5.7.8+
|
||||
- MariaDB v10.2.7+
|
||||
@ -103,7 +119,7 @@ The docker images support the following architectures:
|
||||
The docker images are a manifest of all the architecture docker builds supported, so this means
|
||||
you don't have to worry about doing anything special and you can follow the common instructions above.
|
||||
|
||||
Check out the [dockerhub tags](https://cloud.docker.com/repository/registry-1.docker.io/jc21/nginx-proxy-manager/tags)
|
||||
Check out the [dockerhub tags](https://hub.docker.com/r/jc21/nginx-proxy-manager/tags)
|
||||
for a list of supported architectures and if you want one that doesn't exist,
|
||||
[create a feature request](https://github.com/jc21/nginx-proxy-manager/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=).
|
||||
|
||||
|
3587
docs/yarn.lock
3587
docs/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -53,7 +53,7 @@ function fetch(verb, path, data, options) {
|
||||
contentType: options.contentType || 'application/json; charset=UTF-8',
|
||||
processData: options.processData || true,
|
||||
crossDomain: true,
|
||||
timeout: options.timeout ? options.timeout : 30000,
|
||||
timeout: options.timeout ? options.timeout : 180000,
|
||||
xhrFields: {
|
||||
withCredentials: true
|
||||
},
|
||||
@ -139,7 +139,11 @@ function FileUpload(path, fd) {
|
||||
xhr.onreadystatechange = function () {
|
||||
if (this.readyState === XMLHttpRequest.DONE) {
|
||||
if (xhr.status !== 200 && xhr.status !== 201) {
|
||||
reject(new Error('Upload failed: ' + xhr.status));
|
||||
try {
|
||||
reject(new Error('Upload failed: ' + JSON.parse(xhr.responseText).error.message));
|
||||
} catch (err) {
|
||||
reject(new Error('Upload failed: ' + xhr.status));
|
||||
}
|
||||
} else {
|
||||
resolve(xhr.responseText);
|
||||
}
|
||||
@ -587,7 +591,9 @@ module.exports = {
|
||||
* @param {Object} data
|
||||
*/
|
||||
create: function (data) {
|
||||
return fetch('post', 'nginx/certificates', data);
|
||||
|
||||
const timeout = 180000 + (data && data.meta && data.meta.propagation_seconds ? Number(data.meta.propagation_seconds) * 1000 : 0);
|
||||
return fetch('post', 'nginx/certificates', data, {timeout});
|
||||
},
|
||||
|
||||
/**
|
||||
@ -630,8 +636,8 @@ module.exports = {
|
||||
* @param {Number} id
|
||||
* @returns {Promise}
|
||||
*/
|
||||
renew: function (id) {
|
||||
return fetch('post', 'nginx/certificates/' + id + '/renew');
|
||||
renew: function (id, timeout = 180000) {
|
||||
return fetch('post', 'nginx/certificates/' + id + '/renew', undefined, {timeout});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -3,28 +3,84 @@
|
||||
<h5 class="modal-title"><%- i18n('access-lists', 'form-title', {id: id}) %></h5>
|
||||
<button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="modal-body has-tabs">
|
||||
<form>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="form-label"><%- i18n('str', 'name') %> <span class="form-required">*</span></label>
|
||||
<input type="text" name="name" class="form-control" value="<%- name %>" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-6">
|
||||
<div class="form-group">
|
||||
<label class="form-label"><%- i18n('str', 'username') %></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-6">
|
||||
<div class="form-group">
|
||||
<label class="form-label"><%- i18n('str', 'password') %></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li role="presentation" class="nav-item"><a href="#details" aria-controls="tab1" role="tab" data-toggle="tab" class="nav-link active show" aria-selected="true"><i class="fe fe-zap"></i> <%- i18n('access-lists', 'details') %></a></li>
|
||||
<li role="presentation" class="nav-item"><a href="#auth" aria-controls="tab4" role="tab" data-toggle="tab" class="nav-link" aria-selected="false"><i class="fe fe-users"></i> <%- i18n('access-lists', 'authorization') %></a></li>
|
||||
<li role="presentation" class="nav-item"><a href="#access" aria-controls="tab2" role="tab" data-toggle="tab" class="nav-link" aria-selected="false"><i class="fe fe-radio"></i> <%- i18n('access-lists', 'access') %></a></li>
|
||||
</ul>
|
||||
|
||||
<div class="items"><!-- items --></div>
|
||||
<div class="tab-content">
|
||||
<!-- Details -->
|
||||
<div role="tabpanel" class="tab-pane active show" id="details">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="form-label"><%- i18n('str', 'name') %> <span class="form-required">*</span></label>
|
||||
<input type="text" name="name" class="form-control" value="<%- name %>" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-md-6">
|
||||
<div class="form-group">
|
||||
<label class="custom-switch">
|
||||
<input type="checkbox" class="custom-switch-input" name="satisfy_any" value="1"<%- typeof satisfy_any !== 'undefined' && satisfy_any ? ' checked' : '' %>>
|
||||
<span class="custom-switch-indicator"></span>
|
||||
<span class="custom-switch-description"><%- i18n('access-lists', 'satisfy-any') %></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-md-6">
|
||||
<div class="form-group">
|
||||
<label class="custom-switch">
|
||||
<input type="checkbox" class="custom-switch-input" name="pass_auth" value="1"<%- typeof pass_auth !== 'undefined' && pass_auth ? ' checked' : '' %>>
|
||||
<span class="custom-switch-indicator"></span>
|
||||
<span class="custom-switch-description"><%- i18n('access-lists', 'pass-auth') %></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Authorization -->
|
||||
<div class="tab-pane" id="auth">
|
||||
<div class="row">
|
||||
<div class="col-sm-6 col-md-6">
|
||||
<div class="form-group">
|
||||
<label class="form-label"><%- i18n('str', 'username') %></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-6">
|
||||
<div class="form-group">
|
||||
<label class="form-label"><%- i18n('str', 'password') %></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="items"><!-- items --></div>
|
||||
</div>
|
||||
|
||||
<!-- Access -->
|
||||
<div class="tab-pane" id="access">
|
||||
<div class="clients"><!-- clients --></div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 col-md-3">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control disabled" value="deny" disabled>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-9 col-md-9">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control disabled" value="all" disabled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-muted">Note that the <code>allow</code> and <code>deny</code> directives will be applied in the order they are defined.</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
@ -3,6 +3,7 @@ const App = require('../../main');
|
||||
const AccessListModel = require('../../../models/access-list');
|
||||
const template = require('./form.ejs');
|
||||
const ItemView = require('./form/item');
|
||||
const ClientView = require('./form/client');
|
||||
|
||||
require('jquery-serializejson');
|
||||
|
||||
@ -10,20 +11,26 @@ const ItemsView = Mn.CollectionView.extend({
|
||||
childView: ItemView
|
||||
});
|
||||
|
||||
const ClientsView = Mn.CollectionView.extend({
|
||||
childView: ClientView
|
||||
});
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
template: template,
|
||||
className: 'modal-dialog',
|
||||
|
||||
ui: {
|
||||
items_region: '.items',
|
||||
form: 'form',
|
||||
buttons: '.modal-footer button',
|
||||
cancel: 'button.cancel',
|
||||
save: 'button.save'
|
||||
items_region: '.items',
|
||||
clients_region: '.clients',
|
||||
form: 'form',
|
||||
buttons: '.modal-footer button',
|
||||
cancel: 'button.cancel',
|
||||
save: 'button.save'
|
||||
},
|
||||
|
||||
regions: {
|
||||
items_region: '@ui.items_region'
|
||||
items_region: '@ui.items_region',
|
||||
clients_region: '@ui.clients_region'
|
||||
},
|
||||
|
||||
events: {
|
||||
@ -35,9 +42,10 @@ module.exports = Mn.View.extend({
|
||||
return;
|
||||
}
|
||||
|
||||
let view = this;
|
||||
let form_data = this.ui.form.serializeJSON();
|
||||
let items_data = [];
|
||||
let view = this;
|
||||
let form_data = this.ui.form.serializeJSON();
|
||||
let items_data = [];
|
||||
let clients_data = [];
|
||||
|
||||
form_data.username.map(function (val, idx) {
|
||||
if (val.trim().length) {
|
||||
@ -48,16 +56,30 @@ module.exports = Mn.View.extend({
|
||||
}
|
||||
});
|
||||
|
||||
if (!items_data.length) {
|
||||
alert('You must specify at least 1 Username and Password combination');
|
||||
form_data.address.map(function (val, idx) {
|
||||
if (val.trim().length) {
|
||||
clients_data.push({
|
||||
address: val.trim(),
|
||||
directive: form_data.directive[idx]
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
if (!items_data.length && !clients_data.length) {
|
||||
alert('You must specify at least 1 Authorization or Access rule');
|
||||
return;
|
||||
}
|
||||
|
||||
let data = {
|
||||
name: form_data.name,
|
||||
items: items_data
|
||||
name: form_data.name,
|
||||
satisfy_any: !!form_data.satisfy_any,
|
||||
pass_auth: !!form_data.pass_auth,
|
||||
items: items_data,
|
||||
clients: clients_data
|
||||
};
|
||||
|
||||
console.log(data);
|
||||
|
||||
let method = App.Api.Nginx.AccessLists.create;
|
||||
let is_new = true;
|
||||
|
||||
@ -88,6 +110,7 @@ module.exports = Mn.View.extend({
|
||||
|
||||
onRender: function () {
|
||||
let items = this.model.get('items');
|
||||
let clients = this.model.get('clients');
|
||||
|
||||
// Add empty items to the end of the list. This is cheating but hey I don't have the time to do it right
|
||||
let items_to_add = 5 - items.length;
|
||||
@ -97,9 +120,20 @@ module.exports = Mn.View.extend({
|
||||
}
|
||||
}
|
||||
|
||||
let clients_to_add = 4 - clients.length;
|
||||
if (clients_to_add) {
|
||||
for (let i = 0; i < clients_to_add; i++) {
|
||||
clients.push({});
|
||||
}
|
||||
}
|
||||
|
||||
this.showChildView('items_region', new ItemsView({
|
||||
collection: new Backbone.Collection(items)
|
||||
}));
|
||||
|
||||
this.showChildView('clients_region', new ClientsView({
|
||||
collection: new Backbone.Collection(clients)
|
||||
}));
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
|
13
frontend/js/app/nginx/access/form/client.ejs
Normal file
13
frontend/js/app/nginx/access/form/client.ejs
Normal file
@ -0,0 +1,13 @@
|
||||
<div class="col-sm-3 col-md-3">
|
||||
<div class="form-group">
|
||||
<select name="directive[]" class="form-control custom-select" placeholder="http">
|
||||
<option value="allow" <%- typeof directive == 'undefined' || directive === 'allow' ? 'selected' : '' %>>allow</option>
|
||||
<option value="deny" <%- typeof directive !== 'undefined' && directive === 'deny' ? 'selected' : '' %>>deny</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-9 col-md-9">
|
||||
<div class="form-group">
|
||||
<input type="text" name="address[]" class="form-control" value="<%- typeof address !== 'undefined' ? address : '' %>" value="">
|
||||
</div>
|
||||
</div>
|
7
frontend/js/app/nginx/access/form/client.js
Normal file
7
frontend/js/app/nginx/access/form/client.js
Normal file
@ -0,0 +1,7 @@
|
||||
const Mn = require('backbone.marionette');
|
||||
const template = require('./client.ejs');
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
template: template,
|
||||
className: 'row'
|
||||
});
|
@ -14,6 +14,16 @@
|
||||
<td>
|
||||
<%- i18n('access-lists', 'item-count', {count: items.length || 0}) %>
|
||||
</td>
|
||||
<td>
|
||||
<%- i18n('access-lists', 'client-count', {count: clients.length || 0}) %>
|
||||
</td>
|
||||
<td>
|
||||
<% if (satisfy_any) { %>
|
||||
<%- i18n('str', 'any') %>
|
||||
<%} else { %>
|
||||
<%- i18n('str', 'all') %>
|
||||
<% } %>
|
||||
</td>
|
||||
<td>
|
||||
<%- i18n('access-lists', 'proxy-host-count', {count: proxy_host_count}) %>
|
||||
</td>
|
||||
|
@ -1,7 +1,9 @@
|
||||
<thead>
|
||||
<th width="30"> </th>
|
||||
<th><%- i18n('str', 'name') %></th>
|
||||
<th><%- i18n('users', 'title') %></th>
|
||||
<th><%- i18n('access-lists', 'authorization') %></th>
|
||||
<th><%- i18n('access-lists', 'access') %></th>
|
||||
<th><%- i18n('access-lists', 'satisfy') %></th>
|
||||
<th><%- i18n('proxy-hosts', 'title') %></th>
|
||||
<% if (canManage) { %>
|
||||
<th> </th>
|
||||
|
@ -40,7 +40,7 @@ module.exports = Mn.View.extend({
|
||||
onRender: function () {
|
||||
let view = this;
|
||||
|
||||
App.Api.Nginx.AccessLists.getAll(['owner', 'items'])
|
||||
App.Api.Nginx.AccessLists.getAll(['owner', 'items', 'clients'])
|
||||
.then(response => {
|
||||
if (!view.isDestroyed()) {
|
||||
if (response && response.length) {
|
||||
|
@ -1,10 +1,15 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title"><%- i18n('certificates', 'form-title', {provider: provider}) %></h5>
|
||||
<button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button>
|
||||
<button type="button" class="close cancel non-loader-content" aria-label="Close" data-dismiss="modal"> </button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form>
|
||||
<div class="alert alert-danger mb-0 rounded-0" id="le-error-info" role="alert"></div>
|
||||
<div class="text-center loader-content">
|
||||
<div class="loader mx-auto my-6"></div>
|
||||
<p><%- i18n('ssl', 'processing-info') %></p>
|
||||
</div>
|
||||
<form class="non-loader-content">
|
||||
<div class="row">
|
||||
<% if (provider === 'letsencrypt') { %>
|
||||
<div class="col-sm-12 col-md-12">
|
||||
@ -20,6 +25,99 @@
|
||||
<input name="meta[letsencrypt_email]" type="email" class="form-control" placeholder="" value="<%- getLetsencryptEmail() %>" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DNS challenge -->
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="custom-switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="custom-switch-input"
|
||||
name="meta[dns_challenge]"
|
||||
value="1"
|
||||
<%- getUseDnsChallenge() ? 'checked' : '' %>
|
||||
>
|
||||
<span class="custom-switch-indicator"></span>
|
||||
<span class="custom-switch-description"><%= i18n('ssl', 'dns-challenge') %></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<fieldset class="form-fieldset dns-challenge">
|
||||
<div class="text-red mb-4"><i class="fe fe-alert-triangle"></i> <%= i18n('ssl', 'certbot-warning') %></div>
|
||||
|
||||
<!-- Certbot DNS plugin selection -->
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="form-label"><%- i18n('ssl', 'dns-provider') %> <span class="form-required">*</span></label>
|
||||
<select
|
||||
name="meta[dns_provider]"
|
||||
id="dns_provider"
|
||||
class="form-control custom-select"
|
||||
>
|
||||
<option
|
||||
value=""
|
||||
disabled
|
||||
hidden
|
||||
<%- getDnsProvider() === null ? 'selected' : '' %>
|
||||
>Please Choose...</option>
|
||||
<% _.each(dns_plugins, function(plugin_info, plugin_name){ %>
|
||||
<option
|
||||
value="<%- plugin_name %>"
|
||||
<%- getDnsProvider() === plugin_name ? 'selected' : '' %>
|
||||
><%- plugin_info.display_name %></option>
|
||||
<% }); %>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Certbot credentials file content -->
|
||||
<div class="row credentials-file-content">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="form-label"><%- i18n('ssl', 'credentials-file-content') %> <span class="form-required">*</span></label>
|
||||
<textarea
|
||||
name="meta[dns_provider_credentials]"
|
||||
class="form-control text-monospace"
|
||||
id="dns_provider_credentials"
|
||||
><%- getDnsProviderCredentials() %></textarea>
|
||||
<div class="text-secondary small">
|
||||
<i class="fe fe-info"></i>
|
||||
<%= i18n('ssl', 'credentials-file-content-info') %>
|
||||
</div>
|
||||
<div class="text-red small">
|
||||
<i class="fe fe-alert-triangle"></i>
|
||||
<%= i18n('ssl', 'stored-as-plaintext-info') %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DNS propagation delay -->
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="form-group mb-0">
|
||||
<label class="form-label"><%- i18n('ssl', 'propagation-seconds') %></label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
name="meta[propagation_seconds]"
|
||||
class="form-control"
|
||||
id="propagation_seconds"
|
||||
value="<%- getPropagationSeconds() %>"
|
||||
>
|
||||
<div class="text-secondary small">
|
||||
<i class="fe fe-info"></i>
|
||||
<%= i18n('ssl', 'propagation-seconds-info') %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="custom-switch">
|
||||
@ -42,7 +140,7 @@
|
||||
<div class="form-label"><%- i18n('certificates', 'other-certificate-key') %><span class="form-required">*</span></div>
|
||||
<div class="custom-file">
|
||||
<input type="file" class="custom-file-input" name="meta[other_certificate_key]" id="other_certificate_key" required>
|
||||
<label class="custom-file-label"><%- i18n('str', 'choose-file') %></label>
|
||||
<label id="other_certificate_key_label" class="custom-file-label"><%- i18n('str', 'choose-file') %></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -51,7 +149,7 @@
|
||||
<div class="form-label"><%- i18n('certificates', 'other-certificate') %><span class="form-required">*</span></div>
|
||||
<div class="custom-file">
|
||||
<input type="file" class="custom-file-input" name="meta[other_certificate]" id="other_certificate">
|
||||
<label class="custom-file-label"><%- i18n('str', 'choose-file') %></label>
|
||||
<label id="other_certificate_label" class="custom-file-label"><%- i18n('str', 'choose-file') %></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -60,7 +158,7 @@
|
||||
<div class="form-label"><%- i18n('certificates', 'other-intermediate-certificate') %></div>
|
||||
<div class="custom-file">
|
||||
<input type="file" class="custom-file-input" name="meta[other_intermediate_certificate]" id="other_intermediate_certificate">
|
||||
<label class="custom-file-label"><%- i18n('str', 'choose-file') %></label>
|
||||
<label id="other_intermediate_certificate_label" class="custom-file-label"><%- i18n('str', 'choose-file') %></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -69,7 +167,7 @@
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="modal-footer non-loader-content">
|
||||
<button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'cancel') %></button>
|
||||
<button type="button" class="btn btn-teal save"><%- i18n('str', 'save') %></button>
|
||||
</div>
|
||||
|
@ -3,6 +3,8 @@ const Mn = require('backbone.marionette');
|
||||
const App = require('../../main');
|
||||
const CertificateModel = require('../../../models/certificate');
|
||||
const template = require('./form.ejs');
|
||||
const i18n = require('../../i18n');
|
||||
const dns_providers = require('../../../../../global/certbot-dns-plugins');
|
||||
|
||||
require('jquery-serializejson');
|
||||
require('selectize');
|
||||
@ -13,42 +15,105 @@ module.exports = Mn.View.extend({
|
||||
max_file_size: 102400,
|
||||
|
||||
ui: {
|
||||
form: 'form',
|
||||
domain_names: 'input[name="domain_names"]',
|
||||
buttons: '.modal-footer button',
|
||||
cancel: 'button.cancel',
|
||||
save: 'button.save',
|
||||
other_certificate: '#other_certificate',
|
||||
other_certificate_key: '#other_certificate_key',
|
||||
other_intermediate_certificate: '#other_intermediate_certificate'
|
||||
form: 'form',
|
||||
loader_content: '.loader-content',
|
||||
non_loader_content: '.non-loader-content',
|
||||
le_error_info: '#le-error-info',
|
||||
domain_names: 'input[name="domain_names"]',
|
||||
buttons: '.modal-footer button',
|
||||
cancel: 'button.cancel',
|
||||
save: 'button.save',
|
||||
other_certificate: '#other_certificate',
|
||||
other_certificate_label: '#other_certificate_label',
|
||||
other_certificate_key: '#other_certificate_key',
|
||||
dns_challenge_switch: 'input[name="meta[dns_challenge]"]',
|
||||
dns_challenge_content: '.dns-challenge',
|
||||
dns_provider: 'select[name="meta[dns_provider]"]',
|
||||
credentials_file_content: '.credentials-file-content',
|
||||
dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]',
|
||||
propagation_seconds: 'input[name="meta[propagation_seconds]"]',
|
||||
other_certificate_key_label: '#other_certificate_key_label',
|
||||
other_intermediate_certificate: '#other_intermediate_certificate',
|
||||
other_intermediate_certificate_label: '#other_intermediate_certificate_label'
|
||||
},
|
||||
|
||||
|
||||
events: {
|
||||
'change @ui.dns_challenge_switch': function () {
|
||||
const checked = this.ui.dns_challenge_switch.prop('checked');
|
||||
if (checked) {
|
||||
this.ui.dns_provider.prop('required', 'required');
|
||||
const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value;
|
||||
if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){
|
||||
this.ui.dns_provider_credentials.prop('required', 'required');
|
||||
}
|
||||
this.ui.dns_challenge_content.show();
|
||||
} else {
|
||||
this.ui.dns_provider.prop('required', false);
|
||||
this.ui.dns_provider_credentials.prop('required', false);
|
||||
this.ui.dns_challenge_content.hide();
|
||||
}
|
||||
},
|
||||
|
||||
'change @ui.dns_provider': function () {
|
||||
const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value;
|
||||
if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) {
|
||||
this.ui.dns_provider_credentials.prop('required', 'required');
|
||||
this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials;
|
||||
this.ui.credentials_file_content.show();
|
||||
} else {
|
||||
this.ui.dns_provider_credentials.prop('required', false);
|
||||
this.ui.credentials_file_content.hide();
|
||||
}
|
||||
},
|
||||
|
||||
'click @ui.save': function (e) {
|
||||
e.preventDefault();
|
||||
this.ui.le_error_info.hide();
|
||||
|
||||
if (!this.ui.form[0].checkValidity()) {
|
||||
$('<input type="submit">').hide().appendTo(this.ui.form).click().remove();
|
||||
$(this).removeClass('btn-loading');
|
||||
return;
|
||||
}
|
||||
|
||||
let view = this;
|
||||
let data = this.ui.form.serializeJSON();
|
||||
data.provider = this.model.get('provider');
|
||||
|
||||
// Manipulate
|
||||
if (typeof data.meta !== 'undefined' && typeof data.meta.letsencrypt_agree !== 'undefined') {
|
||||
data.meta.letsencrypt_agree = !!data.meta.letsencrypt_agree;
|
||||
}
|
||||
|
||||
if (typeof data.domain_names === 'string' && data.domain_names) {
|
||||
data.domain_names = data.domain_names.split(',');
|
||||
}
|
||||
|
||||
let ssl_files = [];
|
||||
|
||||
// check files are attached
|
||||
if (this.model.get('provider') === 'other' && !this.model.hasSslFiles()) {
|
||||
if (data.provider === 'letsencrypt') {
|
||||
if (typeof data.meta === 'undefined') data.meta = {};
|
||||
|
||||
let domain_err = false;
|
||||
if (!data.meta.dns_challenge) {
|
||||
data.domain_names.split(',').map(function (name) {
|
||||
if (name.match(/\*/im)) {
|
||||
domain_err = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (domain_err) {
|
||||
alert(i18n('ssl', 'no-wildcard-without-dns'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Manipulate
|
||||
data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1;
|
||||
data.meta.dns_challenge = data.meta.dns_challenge == 1;
|
||||
|
||||
if(!data.meta.dns_challenge){
|
||||
data.meta.dns_provider = undefined;
|
||||
data.meta.dns_provider_credentials = undefined;
|
||||
data.meta.propagation_seconds = undefined;
|
||||
} else {
|
||||
if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined;
|
||||
}
|
||||
|
||||
if (typeof data.domain_names === 'string' && data.domain_names) {
|
||||
data.domain_names = data.domain_names.split(',');
|
||||
}
|
||||
} else if (data.provider === 'other' && !this.model.hasSslFiles()) {
|
||||
// check files are attached
|
||||
if (!this.ui.other_certificate[0].files.length || !this.ui.other_certificate[0].files[0].size) {
|
||||
alert('Certificate file is not attached');
|
||||
return;
|
||||
@ -80,18 +145,19 @@ module.exports = Mn.View.extend({
|
||||
}
|
||||
}
|
||||
|
||||
this.ui.buttons.prop('disabled', true).addClass('btn-disabled');
|
||||
this.ui.loader_content.show();
|
||||
this.ui.non_loader_content.hide();
|
||||
|
||||
// compile file data
|
||||
let form_data = new FormData();
|
||||
if (view.model.get('provider') && ssl_files.length) {
|
||||
if (data.provider === 'other' && ssl_files.length) {
|
||||
ssl_files.map(function (file) {
|
||||
form_data.append(file.name, file.file);
|
||||
});
|
||||
}
|
||||
|
||||
new Promise(resolve => {
|
||||
if (view.model.get('provider') === 'other') {
|
||||
if (data.provider === 'other') {
|
||||
resolve(App.Api.Nginx.Certificates.validate(form_data));
|
||||
} else {
|
||||
resolve();
|
||||
@ -101,13 +167,13 @@ module.exports = Mn.View.extend({
|
||||
return App.Api.Nginx.Certificates.create(data);
|
||||
})
|
||||
.then(result => {
|
||||
view.model.set(result);
|
||||
this.model.set(result);
|
||||
|
||||
// Now upload the certs if we need to
|
||||
if (view.model.get('provider') === 'other') {
|
||||
return App.Api.Nginx.Certificates.upload(view.model.get('id'), form_data)
|
||||
if (data.provider === 'other') {
|
||||
return App.Api.Nginx.Certificates.upload(this.model.get('id'), form_data)
|
||||
.then(result => {
|
||||
view.model.set('meta', _.assign({}, view.model.get('meta'), result));
|
||||
this.model.set('meta', _.assign({}, this.model.get('meta'), result));
|
||||
});
|
||||
}
|
||||
})
|
||||
@ -117,20 +183,52 @@ module.exports = Mn.View.extend({
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
alert(err.message);
|
||||
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
|
||||
let more_info = '';
|
||||
if (err.code === 500 && err.debug) {
|
||||
try{
|
||||
more_info = JSON.parse(err.debug).debug.stack.join("\n");
|
||||
} catch(e) {}
|
||||
}
|
||||
this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `<pre class="mt-3">${more_info}</pre>`:''}`;
|
||||
this.ui.le_error_info.show();
|
||||
this.ui.le_error_info[0].scrollIntoView();
|
||||
this.ui.loader_content.hide();
|
||||
this.ui.non_loader_content.show();
|
||||
});
|
||||
},
|
||||
'change @ui.other_certificate_key': function(e){
|
||||
this.setFileName("other_certificate_key_label", e)
|
||||
},
|
||||
'change @ui.other_certificate': function(e){
|
||||
this.setFileName("other_certificate_label", e)
|
||||
},
|
||||
'change @ui.other_intermediate_certificate': function(e){
|
||||
this.setFileName("other_intermediate_certificate_label", e)
|
||||
}
|
||||
},
|
||||
|
||||
setFileName(ui, e){
|
||||
this.getUI(ui).text(e.target.files[0].name)
|
||||
},
|
||||
templateContext: {
|
||||
getLetsencryptEmail: function () {
|
||||
return typeof this.meta.letsencrypt_email !== 'undefined' ? this.meta.letsencrypt_email : App.Cache.User.get('email');
|
||||
},
|
||||
|
||||
getLetsencryptAgree: function () {
|
||||
return typeof this.meta.letsencrypt_agree !== 'undefined' ? this.meta.letsencrypt_agree : false;
|
||||
}
|
||||
},
|
||||
getUseDnsChallenge: function () {
|
||||
return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false;
|
||||
},
|
||||
getDnsProvider: function () {
|
||||
return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null;
|
||||
},
|
||||
getDnsProviderCredentials: function () {
|
||||
return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : '';
|
||||
},
|
||||
getPropagationSeconds: function () {
|
||||
return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : '';
|
||||
},
|
||||
dns_plugins: dns_providers,
|
||||
},
|
||||
|
||||
onRender: function () {
|
||||
@ -144,8 +242,12 @@ module.exports = Mn.View.extend({
|
||||
text: input
|
||||
};
|
||||
},
|
||||
createFilter: /^(?:[^.*]+\.?)+[^.]$/
|
||||
createFilter: /^(?:[^.]+\.?)+[^.]$/
|
||||
});
|
||||
this.ui.dns_challenge_content.hide();
|
||||
this.ui.credentials_file_content.hide();
|
||||
this.ui.loader_content.hide();
|
||||
this.ui.le_error_info.hide();
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
|
@ -28,7 +28,7 @@
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<%- i18n('ssl', provider) %>
|
||||
<%- i18n('ssl', provider) %><% if (meta.dns_provider) { %> - <%- dns_providers[meta.dns_provider].display_name %><% } %>
|
||||
</td>
|
||||
<td class="<%- isExpired() ? 'text-danger' : '' %>">
|
||||
<%- formatDbDate(expires_on, 'Do MMMM YYYY, h:mm a') %>
|
||||
|
@ -1,7 +1,8 @@
|
||||
const Mn = require('backbone.marionette');
|
||||
const moment = require('moment');
|
||||
const App = require('../../../main');
|
||||
const template = require('./item.ejs');
|
||||
const Mn = require('backbone.marionette');
|
||||
const moment = require('moment');
|
||||
const App = require('../../../main');
|
||||
const template = require('./item.ejs');
|
||||
const dns_providers = require('../../../../../../global/certbot-dns-plugins')
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
template: template,
|
||||
@ -35,7 +36,8 @@ module.exports = Mn.View.extend({
|
||||
canManage: App.Cache.User.canManage('certificates'),
|
||||
isExpired: function () {
|
||||
return moment(this.expires_on).isBefore(moment());
|
||||
}
|
||||
},
|
||||
dns_providers: dns_providers
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
|
@ -4,6 +4,7 @@
|
||||
<button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button>
|
||||
</div>
|
||||
<div class="modal-body has-tabs">
|
||||
<div class="alert alert-danger mb-0 rounded-0" id="le-error-info" role="alert"></div>
|
||||
<form>
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li role="presentation" class="nav-item"><a href="#details" aria-controls="tab1" role="tab" data-toggle="tab" class="nav-link active"><i class="fe fe-zap"></i> <%- i18n('all-hosts', 'details') %></a></li>
|
||||
@ -73,6 +74,98 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DNS challenge -->
|
||||
<div class="col-sm-12 col-md-12 letsencrypt">
|
||||
<div class="form-group">
|
||||
<label class="custom-switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="custom-switch-input"
|
||||
name="meta[dns_challenge]"
|
||||
value="1"
|
||||
<%- getUseDnsChallenge() ? 'checked' : '' %>
|
||||
>
|
||||
<span class="custom-switch-indicator"></span>
|
||||
<span class="custom-switch-description"><%= i18n('ssl', 'dns-challenge') %></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 letsencrypt">
|
||||
<fieldset class="form-fieldset dns-challenge">
|
||||
<div class="text-red mb-4"><i class="fe fe-alert-triangle"></i> <%= i18n('ssl', 'certbot-warning') %></div>
|
||||
|
||||
<!-- Certbot DNS plugin selection -->
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="form-label"><%- i18n('ssl', 'dns-provider') %> <span class="form-required">*</span></label>
|
||||
<select
|
||||
name="meta[dns_provider]"
|
||||
id="dns_provider"
|
||||
class="form-control custom-select"
|
||||
>
|
||||
<option
|
||||
value=""
|
||||
disabled
|
||||
hidden
|
||||
<%- getDnsProvider() === null ? 'selected' : '' %>
|
||||
>Please Choose...</option>
|
||||
<% _.each(dns_plugins, function(plugin_info, plugin_name){ %>
|
||||
<option
|
||||
value="<%- plugin_name %>"
|
||||
<%- getDnsProvider() === plugin_name ? 'selected' : '' %>
|
||||
><%- plugin_info.display_name %></option>
|
||||
<% }); %>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Certbot credentials file content -->
|
||||
<div class="row credentials-file-content">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="form-label"><%- i18n('ssl', 'credentials-file-content') %> <span class="form-required">*</span></label>
|
||||
<textarea
|
||||
name="meta[dns_provider_credentials]"
|
||||
class="form-control text-monospace"
|
||||
id="dns_provider_credentials"
|
||||
><%- getDnsProviderCredentials() %></textarea>
|
||||
<div class="text-secondary small">
|
||||
<i class="fe fe-info"></i>
|
||||
<%= i18n('ssl', 'credentials-file-content-info') %>
|
||||
</div>
|
||||
<div class="text-red small">
|
||||
<i class="fe fe-alert-triangle"></i>
|
||||
<%= i18n('ssl', 'stored-as-plaintext-info') %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DNS propagation delay -->
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="form-group mb-0">
|
||||
<label class="form-label"><%- i18n('ssl', 'propagation-seconds') %></label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
name="meta[propagation_seconds]"
|
||||
class="form-control"
|
||||
id="propagation_seconds"
|
||||
value="<%- getPropagationSeconds() %>"
|
||||
>
|
||||
<div class="text-secondary small">
|
||||
<i class="fe fe-info"></i>
|
||||
<%= i18n('ssl', 'propagation-seconds-info') %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<!-- Lets encrypt -->
|
||||
<div class="col-sm-12 col-md-12 letsencrypt">
|
||||
<div class="form-group">
|
||||
|
@ -4,6 +4,8 @@ const DeadHostModel = require('../../../models/dead-host');
|
||||
const template = require('./form.ejs');
|
||||
const certListItemTemplate = require('../certificates-list-item.ejs');
|
||||
const Helpers = require('../../../lib/helpers');
|
||||
const i18n = require('../../i18n');
|
||||
const dns_providers = require('../../../../../global/certbot-dns-plugins');
|
||||
|
||||
require('jquery-serializejson');
|
||||
require('selectize');
|
||||
@ -13,17 +15,24 @@ module.exports = Mn.View.extend({
|
||||
className: 'modal-dialog',
|
||||
|
||||
ui: {
|
||||
form: 'form',
|
||||
domain_names: 'input[name="domain_names"]',
|
||||
buttons: '.modal-footer button',
|
||||
cancel: 'button.cancel',
|
||||
save: 'button.save',
|
||||
certificate_select: 'select[name="certificate_id"]',
|
||||
ssl_forced: 'input[name="ssl_forced"]',
|
||||
hsts_enabled: 'input[name="hsts_enabled"]',
|
||||
hsts_subdomains: 'input[name="hsts_subdomains"]',
|
||||
http2_support: 'input[name="http2_support"]',
|
||||
letsencrypt: '.letsencrypt'
|
||||
form: 'form',
|
||||
domain_names: 'input[name="domain_names"]',
|
||||
buttons: '.modal-footer button',
|
||||
cancel: 'button.cancel',
|
||||
save: 'button.save',
|
||||
le_error_info: '#le-error-info',
|
||||
certificate_select: 'select[name="certificate_id"]',
|
||||
ssl_forced: 'input[name="ssl_forced"]',
|
||||
hsts_enabled: 'input[name="hsts_enabled"]',
|
||||
hsts_subdomains: 'input[name="hsts_subdomains"]',
|
||||
http2_support: 'input[name="http2_support"]',
|
||||
dns_challenge_switch: 'input[name="meta[dns_challenge]"]',
|
||||
dns_challenge_content: '.dns-challenge',
|
||||
dns_provider: 'select[name="meta[dns_provider]"]',
|
||||
credentials_file_content: '.credentials-file-content',
|
||||
dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]',
|
||||
propagation_seconds: 'input[name="meta[propagation_seconds]"]',
|
||||
letsencrypt: '.letsencrypt'
|
||||
},
|
||||
|
||||
events: {
|
||||
@ -31,10 +40,12 @@ module.exports = Mn.View.extend({
|
||||
let id = this.ui.certificate_select.val();
|
||||
if (id === 'new') {
|
||||
this.ui.letsencrypt.show().find('input').prop('disabled', false);
|
||||
this.ui.dns_challenge_content.hide();
|
||||
} else {
|
||||
this.ui.letsencrypt.hide().find('input').prop('disabled', true);
|
||||
}
|
||||
|
||||
|
||||
let enabled = id === 'new' || parseInt(id, 10) > 0;
|
||||
|
||||
let inputs = this.ui.ssl_forced.add(this.ui.http2_support);
|
||||
@ -76,8 +87,37 @@ module.exports = Mn.View.extend({
|
||||
}
|
||||
},
|
||||
|
||||
'change @ui.dns_challenge_switch': function () {
|
||||
const checked = this.ui.dns_challenge_switch.prop('checked');
|
||||
if (checked) {
|
||||
this.ui.dns_provider.prop('required', 'required');
|
||||
const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value;
|
||||
if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){
|
||||
this.ui.dns_provider_credentials.prop('required', 'required');
|
||||
}
|
||||
this.ui.dns_challenge_content.show();
|
||||
} else {
|
||||
this.ui.dns_provider.prop('required', false);
|
||||
this.ui.dns_provider_credentials.prop('required', false);
|
||||
this.ui.dns_challenge_content.hide();
|
||||
}
|
||||
},
|
||||
|
||||
'change @ui.dns_provider': function () {
|
||||
const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value;
|
||||
if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) {
|
||||
this.ui.dns_provider_credentials.prop('required', 'required');
|
||||
this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials;
|
||||
this.ui.credentials_file_content.show();
|
||||
} else {
|
||||
this.ui.dns_provider_credentials.prop('required', false);
|
||||
this.ui.credentials_file_content.hide();
|
||||
}
|
||||
},
|
||||
|
||||
'click @ui.save': function (e) {
|
||||
e.preventDefault();
|
||||
this.ui.le_error_info.hide();
|
||||
|
||||
if (!this.ui.form[0].checkValidity()) {
|
||||
$('<input type="submit">').hide().appendTo(this.ui.form).click().remove();
|
||||
@ -88,30 +128,42 @@ module.exports = Mn.View.extend({
|
||||
let data = this.ui.form.serializeJSON();
|
||||
|
||||
// Manipulate
|
||||
data.hsts_enabled = !!data.hsts_enabled;
|
||||
data.hsts_subdomains = !!data.hsts_subdomains;
|
||||
data.http2_support = !!data.http2_support;
|
||||
data.ssl_forced = !!data.ssl_forced;
|
||||
data.hsts_enabled = !!data.hsts_enabled;
|
||||
data.hsts_subdomains = !!data.hsts_subdomains;
|
||||
data.http2_support = !!data.http2_support;
|
||||
data.ssl_forced = !!data.ssl_forced;
|
||||
|
||||
if (typeof data.meta === 'undefined') data.meta = {};
|
||||
data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1;
|
||||
data.meta.dns_challenge = data.meta.dns_challenge == 1;
|
||||
|
||||
if(!data.meta.dns_challenge){
|
||||
data.meta.dns_provider = undefined;
|
||||
data.meta.dns_provider_credentials = undefined;
|
||||
data.meta.propagation_seconds = undefined;
|
||||
} else {
|
||||
if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined;
|
||||
}
|
||||
|
||||
if (typeof data.domain_names === 'string' && data.domain_names) {
|
||||
data.domain_names = data.domain_names.split(',');
|
||||
}
|
||||
|
||||
// Check for any domain names containing wildcards, which are not allowed with letsencrypt
|
||||
if (data.certificate_id === 'new') {
|
||||
if (data.certificate_id === 'new') {
|
||||
let domain_err = false;
|
||||
data.domain_names.map(function (name) {
|
||||
if (name.match(/\*/im)) {
|
||||
domain_err = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (domain_err) {
|
||||
alert('Cannot request Let\'s Encrypt Certificate for wildcard domains');
|
||||
return;
|
||||
if (!data.meta.dns_challenge) {
|
||||
data.domain_names.map(function (name) {
|
||||
if (name.match(/\*/im)) {
|
||||
domain_err = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
data.meta.letsencrypt_agree = data.meta.letsencrypt_agree === '1';
|
||||
if (domain_err) {
|
||||
alert(i18n('ssl', 'no-wildcard-without-dns'));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
data.certificate_id = parseInt(data.certificate_id, 10);
|
||||
}
|
||||
@ -127,6 +179,8 @@ module.exports = Mn.View.extend({
|
||||
}
|
||||
|
||||
this.ui.buttons.prop('disabled', true).addClass('btn-disabled');
|
||||
this.ui.save.addClass('btn-loading');
|
||||
|
||||
method(data)
|
||||
.then(result => {
|
||||
view.model.set(result);
|
||||
@ -138,8 +192,17 @@ module.exports = Mn.View.extend({
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
alert(err.message);
|
||||
let more_info = '';
|
||||
if(err.code === 500 && err.debug){
|
||||
try{
|
||||
more_info = JSON.parse(err.debug).debug.stack.join("\n");
|
||||
} catch(e) {}
|
||||
}
|
||||
this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `<pre class="mt-3">${more_info}</pre>`:''}`;
|
||||
this.ui.le_error_info.show();
|
||||
this.ui.le_error_info[0].scrollIntoView();
|
||||
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
|
||||
this.ui.save.removeClass('btn-loading');
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -147,7 +210,20 @@ module.exports = Mn.View.extend({
|
||||
templateContext: {
|
||||
getLetsencryptEmail: function () {
|
||||
return App.Cache.User.get('email');
|
||||
}
|
||||
},
|
||||
getUseDnsChallenge: function () {
|
||||
return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false;
|
||||
},
|
||||
getDnsProvider: function () {
|
||||
return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null;
|
||||
},
|
||||
getDnsProviderCredentials: function () {
|
||||
return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : '';
|
||||
},
|
||||
getPropagationSeconds: function () {
|
||||
return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : '';
|
||||
},
|
||||
dns_plugins: dns_providers,
|
||||
},
|
||||
|
||||
onRender: function () {
|
||||
@ -168,6 +244,9 @@ module.exports = Mn.View.extend({
|
||||
});
|
||||
|
||||
// Certificates
|
||||
this.ui.le_error_info.hide();
|
||||
this.ui.dns_challenge_content.hide();
|
||||
this.ui.credentials_file_content.hide();
|
||||
this.ui.letsencrypt.hide();
|
||||
this.ui.certificate_select.selectize({
|
||||
valueField: 'id',
|
||||
|
@ -3,7 +3,7 @@
|
||||
<div class="title">
|
||||
<i class="fe fe-lock text-teal"></i> <%- name %>
|
||||
</div>
|
||||
<span class="description"><%- i18n('access-lists', 'item-count', {count: items.length || 0}) %> – Created: <%- formatDbDate(created_on, 'Do MMMM YYYY, h:mm a') %></span>
|
||||
<span class="description"><%- i18n('access-lists', 'item-count', {count: items.length || 0}) %>, <%- i18n('access-lists', 'client-count', {count: clients.length || 0}) %> – Created: <%- formatDbDate(created_on, 'Do MMMM YYYY, h:mm a') %></span>
|
||||
<% } else { %>
|
||||
<div class="title">
|
||||
<i class="fe fe-unlock text-yellow"></i> <%- i18n('access-lists', 'public') %>
|
||||
|
@ -4,6 +4,7 @@
|
||||
<button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button>
|
||||
</div>
|
||||
<div class="modal-body has-tabs">
|
||||
<div class="alert alert-danger mb-0 rounded-0" id="le-error-info" role="alert"></div>
|
||||
<form>
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li role="presentation" class="nav-item"><a href="#details" aria-controls="tab1" role="tab" data-toggle="tab" class="nav-link active"><i class="fe fe-zap"></i> <%- i18n('all-hosts', 'details') %></a></li>
|
||||
@ -44,7 +45,7 @@
|
||||
<div class="col-sm-5 col-md-5">
|
||||
<div class="form-group">
|
||||
<label class="form-label"><%- i18n('proxy-hosts', 'forward-host') %><span class="form-required">*</span></label>
|
||||
<input type="text" name="forward_host" class="form-control text-monospace" placeholder="" value="<%- forward_host %>" autocomplete="off" maxlength="50" required>
|
||||
<input type="text" name="forward_host" class="form-control text-monospace" placeholder="" value="<%- forward_host %>" autocomplete="off" maxlength="255" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4 col-md-4">
|
||||
@ -141,6 +142,98 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DNS challenge -->
|
||||
<div class="col-sm-12 col-md-12 letsencrypt">
|
||||
<div class="form-group">
|
||||
<label class="custom-switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="custom-switch-input"
|
||||
name="meta[dns_challenge]"
|
||||
value="1"
|
||||
<%- getUseDnsChallenge() ? 'checked' : '' %>
|
||||
>
|
||||
<span class="custom-switch-indicator"></span>
|
||||
<span class="custom-switch-description"><%= i18n('ssl', 'dns-challenge') %></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 letsencrypt">
|
||||
<fieldset class="form-fieldset dns-challenge">
|
||||
<div class="text-red mb-4"><i class="fe fe-alert-triangle"></i> <%= i18n('ssl', 'certbot-warning') %></div>
|
||||
|
||||
<!-- Certbot DNS plugin selection -->
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="form-label"><%- i18n('ssl', 'dns-provider') %> <span class="form-required">*</span></label>
|
||||
<select
|
||||
name="meta[dns_provider]"
|
||||
id="dns_provider"
|
||||
class="form-control custom-select"
|
||||
>
|
||||
<option
|
||||
value=""
|
||||
disabled
|
||||
hidden
|
||||
<%- getDnsProvider() === null ? 'selected' : '' %>
|
||||
>Please Choose...</option>
|
||||
<% _.each(dns_plugins, function(plugin_info, plugin_name){ %>
|
||||
<option
|
||||
value="<%- plugin_name %>"
|
||||
<%- getDnsProvider() === plugin_name ? 'selected' : '' %>
|
||||
><%- plugin_info.display_name %></option>
|
||||
<% }); %>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Certbot credentials file content -->
|
||||
<div class="row credentials-file-content">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="form-label"><%- i18n('ssl', 'credentials-file-content') %> <span class="form-required">*</span></label>
|
||||
<textarea
|
||||
name="meta[dns_provider_credentials]"
|
||||
class="form-control text-monospace"
|
||||
id="dns_provider_credentials"
|
||||
><%- getDnsProviderCredentials() %></textarea>
|
||||
<div class="text-secondary small">
|
||||
<i class="fe fe-info"></i>
|
||||
<%= i18n('ssl', 'credentials-file-content-info') %>
|
||||
</div>
|
||||
<div class="text-red small">
|
||||
<i class="fe fe-alert-triangle"></i>
|
||||
<%= i18n('ssl', 'stored-as-plaintext-info') %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DNS propagation delay -->
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="form-group mb-0">
|
||||
<label class="form-label"><%- i18n('ssl', 'propagation-seconds') %></label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
name="meta[propagation_seconds]"
|
||||
class="form-control"
|
||||
id="propagation_seconds"
|
||||
value="<%- getPropagationSeconds() %>"
|
||||
>
|
||||
<div class="text-secondary small">
|
||||
<i class="fe fe-info"></i>
|
||||
<%= i18n('ssl', 'propagation-seconds-info') %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<!-- Lets encrypt -->
|
||||
<div class="col-sm-12 col-md-12 letsencrypt">
|
||||
<div class="form-group">
|
||||
|
@ -7,6 +7,8 @@ const certListItemTemplate = require('../certificates-list-item.ejs');
|
||||
const accessListItemTemplate = require('./access-list-item.ejs');
|
||||
const CustomLocation = require('./location');
|
||||
const Helpers = require('../../../lib/helpers');
|
||||
const i18n = require('../../i18n');
|
||||
const dns_providers = require('../../../../../global/certbot-dns-plugins');
|
||||
|
||||
|
||||
require('jquery-serializejson');
|
||||
@ -19,22 +21,29 @@ module.exports = Mn.View.extend({
|
||||
locationsCollection: new ProxyLocationModel.Collection(),
|
||||
|
||||
ui: {
|
||||
form: 'form',
|
||||
domain_names: 'input[name="domain_names"]',
|
||||
forward_host: 'input[name="forward_host"]',
|
||||
buttons: '.modal-footer button',
|
||||
cancel: 'button.cancel',
|
||||
save: 'button.save',
|
||||
add_location_btn: 'button.add_location',
|
||||
locations_container:'.locations_container',
|
||||
certificate_select: 'select[name="certificate_id"]',
|
||||
access_list_select: 'select[name="access_list_id"]',
|
||||
ssl_forced: 'input[name="ssl_forced"]',
|
||||
hsts_enabled: 'input[name="hsts_enabled"]',
|
||||
hsts_subdomains: 'input[name="hsts_subdomains"]',
|
||||
http2_support: 'input[name="http2_support"]',
|
||||
forward_scheme: 'select[name="forward_scheme"]',
|
||||
letsencrypt: '.letsencrypt'
|
||||
form: 'form',
|
||||
domain_names: 'input[name="domain_names"]',
|
||||
forward_host: 'input[name="forward_host"]',
|
||||
buttons: '.modal-footer button',
|
||||
cancel: 'button.cancel',
|
||||
save: 'button.save',
|
||||
add_location_btn: 'button.add_location',
|
||||
locations_container: '.locations_container',
|
||||
le_error_info: '#le-error-info',
|
||||
certificate_select: 'select[name="certificate_id"]',
|
||||
access_list_select: 'select[name="access_list_id"]',
|
||||
ssl_forced: 'input[name="ssl_forced"]',
|
||||
hsts_enabled: 'input[name="hsts_enabled"]',
|
||||
hsts_subdomains: 'input[name="hsts_subdomains"]',
|
||||
http2_support: 'input[name="http2_support"]',
|
||||
dns_challenge_switch: 'input[name="meta[dns_challenge]"]',
|
||||
dns_challenge_content: '.dns-challenge',
|
||||
dns_provider: 'select[name="meta[dns_provider]"]',
|
||||
credentials_file_content: '.credentials-file-content',
|
||||
dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]',
|
||||
propagation_seconds: 'input[name="meta[propagation_seconds]"]',
|
||||
forward_scheme: 'select[name="forward_scheme"]',
|
||||
letsencrypt: '.letsencrypt'
|
||||
},
|
||||
|
||||
regions: {
|
||||
@ -46,6 +55,7 @@ module.exports = Mn.View.extend({
|
||||
let id = this.ui.certificate_select.val();
|
||||
if (id === 'new') {
|
||||
this.ui.letsencrypt.show().find('input').prop('disabled', false);
|
||||
this.ui.dns_challenge_content.hide();
|
||||
} else {
|
||||
this.ui.letsencrypt.hide().find('input').prop('disabled', true);
|
||||
}
|
||||
@ -91,6 +101,34 @@ module.exports = Mn.View.extend({
|
||||
}
|
||||
},
|
||||
|
||||
'change @ui.dns_challenge_switch': function () {
|
||||
const checked = this.ui.dns_challenge_switch.prop('checked');
|
||||
if (checked) {
|
||||
this.ui.dns_provider.prop('required', 'required');
|
||||
const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value;
|
||||
if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){
|
||||
this.ui.dns_provider_credentials.prop('required', 'required');
|
||||
}
|
||||
this.ui.dns_challenge_content.show();
|
||||
} else {
|
||||
this.ui.dns_provider.prop('required', false);
|
||||
this.ui.dns_provider_credentials.prop('required', false);
|
||||
this.ui.dns_challenge_content.hide();
|
||||
}
|
||||
},
|
||||
|
||||
'change @ui.dns_provider': function () {
|
||||
const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value;
|
||||
if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) {
|
||||
this.ui.dns_provider_credentials.prop('required', 'required');
|
||||
this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials;
|
||||
this.ui.credentials_file_content.show();
|
||||
} else {
|
||||
this.ui.dns_provider_credentials.prop('required', false);
|
||||
this.ui.credentials_file_content.hide();
|
||||
}
|
||||
},
|
||||
|
||||
'click @ui.add_location_btn': function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
@ -100,6 +138,7 @@ module.exports = Mn.View.extend({
|
||||
|
||||
'click @ui.save': function (e) {
|
||||
e.preventDefault();
|
||||
this.ui.le_error_info.hide();
|
||||
|
||||
if (!this.ui.form[0].checkValidity()) {
|
||||
$('<input type="submit">').hide().appendTo(this.ui.form).click().remove();
|
||||
@ -128,26 +167,38 @@ module.exports = Mn.View.extend({
|
||||
data.hsts_enabled = !!data.hsts_enabled;
|
||||
data.hsts_subdomains = !!data.hsts_subdomains;
|
||||
data.ssl_forced = !!data.ssl_forced;
|
||||
|
||||
if (typeof data.meta === 'undefined') data.meta = {};
|
||||
data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1;
|
||||
data.meta.dns_challenge = data.meta.dns_challenge == 1;
|
||||
|
||||
if(!data.meta.dns_challenge){
|
||||
data.meta.dns_provider = undefined;
|
||||
data.meta.dns_provider_credentials = undefined;
|
||||
data.meta.propagation_seconds = undefined;
|
||||
} else {
|
||||
if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined;
|
||||
}
|
||||
|
||||
if (typeof data.domain_names === 'string' && data.domain_names) {
|
||||
data.domain_names = data.domain_names.split(',');
|
||||
}
|
||||
|
||||
// Check for any domain names containing wildcards, which are not allowed with letsencrypt
|
||||
if (data.certificate_id === 'new') {
|
||||
if (data.certificate_id === 'new') {
|
||||
let domain_err = false;
|
||||
data.domain_names.map(function (name) {
|
||||
if (name.match(/\*/im)) {
|
||||
domain_err = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (domain_err) {
|
||||
alert('Cannot request Let\'s Encrypt Certificate for wildcard domains');
|
||||
return;
|
||||
if (!data.meta.dns_challenge) {
|
||||
data.domain_names.map(function (name) {
|
||||
if (name.match(/\*/im)) {
|
||||
domain_err = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
data.meta.letsencrypt_agree = data.meta.letsencrypt_agree === '1';
|
||||
if (domain_err) {
|
||||
alert(i18n('ssl', 'no-wildcard-without-dns'));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
data.certificate_id = parseInt(data.certificate_id, 10);
|
||||
}
|
||||
@ -163,6 +214,8 @@ module.exports = Mn.View.extend({
|
||||
}
|
||||
|
||||
this.ui.buttons.prop('disabled', true).addClass('btn-disabled');
|
||||
this.ui.save.addClass('btn-loading');
|
||||
|
||||
method(data)
|
||||
.then(result => {
|
||||
view.model.set(result);
|
||||
@ -174,8 +227,17 @@ module.exports = Mn.View.extend({
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
alert(err.message);
|
||||
let more_info = '';
|
||||
if(err.code === 500 && err.debug){
|
||||
try{
|
||||
more_info = JSON.parse(err.debug).debug.stack.join("\n");
|
||||
} catch(e) {}
|
||||
}
|
||||
this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `<pre class="mt-3">${more_info}</pre>`:''}`;
|
||||
this.ui.le_error_info.show();
|
||||
this.ui.le_error_info[0].scrollIntoView();
|
||||
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
|
||||
this.ui.save.removeClass('btn-loading');
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -183,7 +245,20 @@ module.exports = Mn.View.extend({
|
||||
templateContext: {
|
||||
getLetsencryptEmail: function () {
|
||||
return App.Cache.User.get('email');
|
||||
}
|
||||
},
|
||||
getUseDnsChallenge: function () {
|
||||
return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false;
|
||||
},
|
||||
getDnsProvider: function () {
|
||||
return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null;
|
||||
},
|
||||
getDnsProviderCredentials: function () {
|
||||
return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : '';
|
||||
},
|
||||
getPropagationSeconds: function () {
|
||||
return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : '';
|
||||
},
|
||||
dns_plugins: dns_providers,
|
||||
},
|
||||
|
||||
onRender: function () {
|
||||
@ -203,7 +278,7 @@ module.exports = Mn.View.extend({
|
||||
text: input
|
||||
};
|
||||
},
|
||||
createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/
|
||||
createFilter: /^(?:\.)?(?:[^.*]+\.?)+[^.]$/
|
||||
});
|
||||
|
||||
// Access Lists
|
||||
@ -222,7 +297,7 @@ module.exports = Mn.View.extend({
|
||||
}
|
||||
},
|
||||
load: function (query, callback) {
|
||||
App.Api.Nginx.AccessLists.getAll(['items'])
|
||||
App.Api.Nginx.AccessLists.getAll(['items', 'clients'])
|
||||
.then(rows => {
|
||||
callback(rows);
|
||||
})
|
||||
@ -237,6 +312,9 @@ module.exports = Mn.View.extend({
|
||||
});
|
||||
|
||||
// Certificates
|
||||
this.ui.le_error_info.hide();
|
||||
this.ui.dns_challenge_content.hide();
|
||||
this.ui.credentials_file_content.hide();
|
||||
this.ui.letsencrypt.hide();
|
||||
this.ui.certificate_select.selectize({
|
||||
valueField: 'id',
|
||||
|
@ -38,7 +38,7 @@
|
||||
<div class="col-sm-5 col-md-5">
|
||||
<div class="form-group">
|
||||
<label class="form-label"><%- i18n('proxy-hosts', 'forward-host') %><span class="form-required">*</span></label>
|
||||
<input type="text" name="forward_host" class="form-control text-monospace model" placeholder="" value="<%- forward_host %>" autocomplete="off" maxlength="50" required>
|
||||
<input type="text" name="forward_host" class="form-control text-monospace model" placeholder="" value="<%- forward_host %>" autocomplete="off" maxlength="200" required>
|
||||
<span style="font-size: 9px;"><%- i18n('proxy-hosts', 'custom-forward-host-help') %></span>
|
||||
</div>
|
||||
</div>
|
||||
@ -61,4 +61,4 @@
|
||||
<i class="fa fa-trash"></i> <%- i18n('locations', 'delete') %>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -4,6 +4,7 @@
|
||||
<button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button>
|
||||
</div>
|
||||
<div class="modal-body has-tabs">
|
||||
<div class="alert alert-danger mb-0 rounded-0" id="le-error-info" role="alert"></div>
|
||||
<form>
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li role="presentation" class="nav-item"><a href="#details" aria-controls="tab1" role="tab" data-toggle="tab" class="nav-link active"><i class="fe fe-zap"></i> <%- i18n('all-hosts', 'details') %></a></li>
|
||||
@ -97,6 +98,98 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DNS challenge -->
|
||||
<div class="col-sm-12 col-md-12 letsencrypt">
|
||||
<div class="form-group">
|
||||
<label class="custom-switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="custom-switch-input"
|
||||
name="meta[dns_challenge]"
|
||||
value="1"
|
||||
<%- getUseDnsChallenge() ? 'checked' : '' %>
|
||||
>
|
||||
<span class="custom-switch-indicator"></span>
|
||||
<span class="custom-switch-description"><%= i18n('ssl', 'dns-challenge') %></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 letsencrypt">
|
||||
<fieldset class="form-fieldset dns-challenge">
|
||||
<div class="text-red mb-4"><i class="fe fe-alert-triangle"></i> <%= i18n('ssl', 'certbot-warning') %></div>
|
||||
|
||||
<!-- Certbot DNS plugin selection -->
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="form-label"><%- i18n('ssl', 'dns-provider') %> <span class="form-required">*</span></label>
|
||||
<select
|
||||
name="meta[dns_provider]"
|
||||
id="dns_provider"
|
||||
class="form-control custom-select"
|
||||
>
|
||||
<option
|
||||
value=""
|
||||
disabled
|
||||
hidden
|
||||
<%- getDnsProvider() === null ? 'selected' : '' %>
|
||||
>Please Choose...</option>
|
||||
<% _.each(dns_plugins, function(plugin_info, plugin_name){ %>
|
||||
<option
|
||||
value="<%- plugin_name %>"
|
||||
<%- getDnsProvider() === plugin_name ? 'selected' : '' %>
|
||||
><%- plugin_info.display_name %></option>
|
||||
<% }); %>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Certbot credentials file content -->
|
||||
<div class="row credentials-file-content">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="form-label"><%- i18n('ssl', 'credentials-file-content') %> <span class="form-required">*</span></label>
|
||||
<textarea
|
||||
name="meta[dns_provider_credentials]"
|
||||
class="form-control text-monospace"
|
||||
id="dns_provider_credentials"
|
||||
><%- getDnsProviderCredentials() %></textarea>
|
||||
<div class="text-secondary small">
|
||||
<i class="fe fe-info"></i>
|
||||
<%= i18n('ssl', 'credentials-file-content-info') %>
|
||||
</div>
|
||||
<div class="text-red small">
|
||||
<i class="fe fe-alert-triangle"></i>
|
||||
<%= i18n('ssl', 'stored-as-plaintext-info') %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DNS propagation delay -->
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="form-group mb-0">
|
||||
<label class="form-label"><%- i18n('ssl', 'propagation-seconds') %></label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
name="meta[propagation_seconds]"
|
||||
class="form-control"
|
||||
id="propagation_seconds"
|
||||
value="<%- getPropagationSeconds() %>"
|
||||
>
|
||||
<div class="text-secondary small">
|
||||
<i class="fe fe-info"></i>
|
||||
<%= i18n('ssl', 'propagation-seconds-info') %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<!-- Lets encrypt -->
|
||||
<div class="col-sm-12 col-md-12 letsencrypt">
|
||||
<div class="form-group">
|
||||
|
@ -4,6 +4,9 @@ const RedirectionHostModel = require('../../../models/redirection-host');
|
||||
const template = require('./form.ejs');
|
||||
const certListItemTemplate = require('../certificates-list-item.ejs');
|
||||
const Helpers = require('../../../lib/helpers');
|
||||
const i18n = require('../../i18n');
|
||||
const dns_providers = require('../../../../../global/certbot-dns-plugins');
|
||||
|
||||
|
||||
require('jquery-serializejson');
|
||||
require('selectize');
|
||||
@ -13,17 +16,24 @@ module.exports = Mn.View.extend({
|
||||
className: 'modal-dialog',
|
||||
|
||||
ui: {
|
||||
form: 'form',
|
||||
domain_names: 'input[name="domain_names"]',
|
||||
buttons: '.modal-footer button',
|
||||
cancel: 'button.cancel',
|
||||
save: 'button.save',
|
||||
certificate_select: 'select[name="certificate_id"]',
|
||||
ssl_forced: 'input[name="ssl_forced"]',
|
||||
hsts_enabled: 'input[name="hsts_enabled"]',
|
||||
hsts_subdomains: 'input[name="hsts_subdomains"]',
|
||||
http2_support: 'input[name="http2_support"]',
|
||||
letsencrypt: '.letsencrypt'
|
||||
form: 'form',
|
||||
domain_names: 'input[name="domain_names"]',
|
||||
buttons: '.modal-footer button',
|
||||
cancel: 'button.cancel',
|
||||
save: 'button.save',
|
||||
le_error_info: '#le-error-info',
|
||||
certificate_select: 'select[name="certificate_id"]',
|
||||
ssl_forced: 'input[name="ssl_forced"]',
|
||||
hsts_enabled: 'input[name="hsts_enabled"]',
|
||||
hsts_subdomains: 'input[name="hsts_subdomains"]',
|
||||
http2_support: 'input[name="http2_support"]',
|
||||
dns_challenge_switch: 'input[name="meta[dns_challenge]"]',
|
||||
dns_challenge_content: '.dns-challenge',
|
||||
dns_provider: 'select[name="meta[dns_provider]"]',
|
||||
credentials_file_content: '.credentials-file-content',
|
||||
dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]',
|
||||
propagation_seconds: 'input[name="meta[propagation_seconds]"]',
|
||||
letsencrypt: '.letsencrypt'
|
||||
},
|
||||
|
||||
events: {
|
||||
@ -31,6 +41,7 @@ module.exports = Mn.View.extend({
|
||||
let id = this.ui.certificate_select.val();
|
||||
if (id === 'new') {
|
||||
this.ui.letsencrypt.show().find('input').prop('disabled', false);
|
||||
this.ui.dns_challenge_content.hide();
|
||||
} else {
|
||||
this.ui.letsencrypt.hide().find('input').prop('disabled', true);
|
||||
}
|
||||
@ -76,8 +87,37 @@ module.exports = Mn.View.extend({
|
||||
}
|
||||
},
|
||||
|
||||
'change @ui.dns_challenge_switch': function () {
|
||||
const checked = this.ui.dns_challenge_switch.prop('checked');
|
||||
if (checked) {
|
||||
this.ui.dns_provider.prop('required', 'required');
|
||||
const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value;
|
||||
if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){
|
||||
this.ui.dns_provider_credentials.prop('required', 'required');
|
||||
}
|
||||
this.ui.dns_challenge_content.show();
|
||||
} else {
|
||||
this.ui.dns_provider.prop('required', false);
|
||||
this.ui.dns_provider_credentials.prop('required', false);
|
||||
this.ui.dns_challenge_content.hide();
|
||||
}
|
||||
},
|
||||
|
||||
'change @ui.dns_provider': function () {
|
||||
const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value;
|
||||
if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) {
|
||||
this.ui.dns_provider_credentials.prop('required', 'required');
|
||||
this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials;
|
||||
this.ui.credentials_file_content.show();
|
||||
} else {
|
||||
this.ui.dns_provider_credentials.prop('required', false);
|
||||
this.ui.credentials_file_content.hide();
|
||||
}
|
||||
},
|
||||
|
||||
'click @ui.save': function (e) {
|
||||
e.preventDefault();
|
||||
this.ui.le_error_info.hide();
|
||||
|
||||
if (!this.ui.form[0].checkValidity()) {
|
||||
$('<input type="submit">').hide().appendTo(this.ui.form).click().remove();
|
||||
@ -88,32 +128,44 @@ module.exports = Mn.View.extend({
|
||||
let data = this.ui.form.serializeJSON();
|
||||
|
||||
// Manipulate
|
||||
data.block_exploits = !!data.block_exploits;
|
||||
data.preserve_path = !!data.preserve_path;
|
||||
data.http2_support = !!data.http2_support;
|
||||
data.hsts_enabled = !!data.hsts_enabled;
|
||||
data.hsts_subdomains = !!data.hsts_subdomains;
|
||||
data.ssl_forced = !!data.ssl_forced;
|
||||
data.block_exploits = !!data.block_exploits;
|
||||
data.preserve_path = !!data.preserve_path;
|
||||
data.http2_support = !!data.http2_support;
|
||||
data.hsts_enabled = !!data.hsts_enabled;
|
||||
data.hsts_subdomains = !!data.hsts_subdomains;
|
||||
data.ssl_forced = !!data.ssl_forced;
|
||||
|
||||
if (typeof data.meta === 'undefined') data.meta = {};
|
||||
data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1;
|
||||
data.meta.dns_challenge = data.meta.dns_challenge == 1;
|
||||
|
||||
if(!data.meta.dns_challenge){
|
||||
data.meta.dns_provider = undefined;
|
||||
data.meta.dns_provider_credentials = undefined;
|
||||
data.meta.propagation_seconds = undefined;
|
||||
} else {
|
||||
if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined;
|
||||
}
|
||||
|
||||
if (typeof data.domain_names === 'string' && data.domain_names) {
|
||||
data.domain_names = data.domain_names.split(',');
|
||||
}
|
||||
|
||||
// Check for any domain names containing wildcards, which are not allowed with letsencrypt
|
||||
if (data.certificate_id === 'new') {
|
||||
if (data.certificate_id === 'new') {
|
||||
let domain_err = false;
|
||||
data.domain_names.map(function (name) {
|
||||
if (name.match(/\*/im)) {
|
||||
domain_err = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (domain_err) {
|
||||
alert('Cannot request Let\'s Encrypt Certificate for wildcard domains');
|
||||
return;
|
||||
if (!data.meta.dns_challenge) {
|
||||
data.domain_names.map(function (name) {
|
||||
if (name.match(/\*/im)) {
|
||||
domain_err = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
data.meta.letsencrypt_agree = data.meta.letsencrypt_agree === '1';
|
||||
if (domain_err) {
|
||||
alert(i18n('ssl', 'no-wildcard-without-dns'));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
data.certificate_id = parseInt(data.certificate_id, 10);
|
||||
}
|
||||
@ -129,6 +181,8 @@ module.exports = Mn.View.extend({
|
||||
}
|
||||
|
||||
this.ui.buttons.prop('disabled', true).addClass('btn-disabled');
|
||||
this.ui.save.addClass('btn-loading');
|
||||
|
||||
method(data)
|
||||
.then(result => {
|
||||
view.model.set(result);
|
||||
@ -140,8 +194,17 @@ module.exports = Mn.View.extend({
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
alert(err.message);
|
||||
let more_info = '';
|
||||
if(err.code === 500 && err.debug){
|
||||
try{
|
||||
more_info = JSON.parse(err.debug).debug.stack.join("\n");
|
||||
} catch(e) {}
|
||||
}
|
||||
this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `<pre class="mt-3">${more_info}</pre>`:''}`;
|
||||
this.ui.le_error_info.show();
|
||||
this.ui.le_error_info[0].scrollIntoView();
|
||||
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
|
||||
this.ui.save.removeClass('btn-loading');
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -149,7 +212,20 @@ module.exports = Mn.View.extend({
|
||||
templateContext: {
|
||||
getLetsencryptEmail: function () {
|
||||
return App.Cache.User.get('email');
|
||||
}
|
||||
},
|
||||
getUseDnsChallenge: function () {
|
||||
return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false;
|
||||
},
|
||||
getDnsProvider: function () {
|
||||
return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null;
|
||||
},
|
||||
getDnsProviderCredentials: function () {
|
||||
return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : '';
|
||||
},
|
||||
getPropagationSeconds: function () {
|
||||
return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : '';
|
||||
},
|
||||
dns_plugins: dns_providers,
|
||||
},
|
||||
|
||||
onRender: function () {
|
||||
@ -170,6 +246,9 @@ module.exports = Mn.View.extend({
|
||||
});
|
||||
|
||||
// Certificates
|
||||
this.ui.le_error_info.hide();
|
||||
this.ui.dns_challenge_content.hide();
|
||||
this.ui.credentials_file_content.hide();
|
||||
this.ui.letsencrypt.hide();
|
||||
this.ui.certificate_select.selectize({
|
||||
valueField: 'id',
|
||||
|
@ -33,7 +33,9 @@
|
||||
"unknown": "Unknown",
|
||||
"expires": "Expires",
|
||||
"value": "Value",
|
||||
"please-wait": "Please wait..."
|
||||
"please-wait": "Please wait...",
|
||||
"all": "All",
|
||||
"any": "Any"
|
||||
},
|
||||
"login": {
|
||||
"title": "Login to your account"
|
||||
@ -99,7 +101,18 @@
|
||||
"letsencrypt-email": "Email Address for Let's Encrypt",
|
||||
"letsencrypt-agree": "I Agree to the <a href=\"{url}\" target=\"_blank\">Let's Encrypt Terms of Service</a>",
|
||||
"delete-ssl": "The SSL certificates attached will NOT be removed, they will need to be removed manually.",
|
||||
"hosts-warning": "These domains must be already configured to point to this installation"
|
||||
"hosts-warning": "These domains must be already configured to point to this installation",
|
||||
"no-wildcard-without-dns": "Cannot request Let's Encrypt Certificate for wildcard domains when not using DNS challenge",
|
||||
"dns-challenge": "Use a DNS Challenge",
|
||||
"certbot-warning": "This section requires some knowledge about Certbot and its DNS plugins. Please consult the respective plugins documentation.",
|
||||
"dns-provider": "DNS Provider",
|
||||
"please-choose": "Please Choose...",
|
||||
"credentials-file-content": "Credentials File Content",
|
||||
"credentials-file-content-info": "This plugin requires a configuration file containing an API token or other credentials to your provider",
|
||||
"stored-as-plaintext-info": "This data will be stored as plaintext in the database!",
|
||||
"propagation-seconds": "Propagation Seconds",
|
||||
"propagation-seconds-info": "Leave empty to use the plugins default value. Number of seconds to wait for DNS propagation.",
|
||||
"processing-info": "Processing... This might take a few minutes."
|
||||
},
|
||||
"proxy-hosts": {
|
||||
"title": "Proxy Hosts",
|
||||
@ -184,10 +197,17 @@
|
||||
"public": "Publicly Accessible",
|
||||
"public-sub": "No Access Restrictions",
|
||||
"help-title": "What is an Access List?",
|
||||
"help-content": "Access Lists provide authentication for the Proxy Hosts via Basic HTTP Authentication.\nYou can configure multiple usernames and passwords for a single Access List and then apply that to a Proxy Host.\nThis is most useful for forwarded web services that do not have authentication mechanisms built in.",
|
||||
"help-content": "Access Lists provide a blacklist or whitelist of specific client IP addresses along with authentication for the Proxy Hosts via Basic HTTP Authentication.\nYou can configure multiple client rules, usernames and passwords for a single Access List and then apply that to a Proxy Host.\nThis is most useful for forwarded web services that do not have authentication mechanisms built in or that you want to protect from access by unknown clients.",
|
||||
"item-count": "{count} {count, select, 1{User} other{Users}}",
|
||||
"client-count": "{count} {count, select, 1{Rule} other{Rules}}",
|
||||
"proxy-host-count": "{count} {count, select, 1{Proxy Host} other{Proxy Hosts}}",
|
||||
"delete-has-hosts": "This Access List is associated with {count} Proxy Hosts. They will become publicly available upon deletion."
|
||||
"delete-has-hosts": "This Access List is associated with {count} Proxy Hosts. They will become publicly available upon deletion.",
|
||||
"details": "Details",
|
||||
"authorization": "Authorization",
|
||||
"access": "Access",
|
||||
"satisfy": "Satisfy",
|
||||
"satisfy-any": "Satisfy Any",
|
||||
"pass-auth": "Pass Auth to Host"
|
||||
},
|
||||
"users": {
|
||||
"title": "Users",
|
||||
|
@ -10,6 +10,7 @@ const model = Backbone.Model.extend({
|
||||
modified_on: null,
|
||||
name: '',
|
||||
items: [],
|
||||
clients: [],
|
||||
// The following are expansions:
|
||||
owner: null
|
||||
};
|
||||
|
@ -19,7 +19,7 @@
|
||||
"file-loader": "^6.0.0",
|
||||
"html-webpack-plugin": "^4.0.4",
|
||||
"imports-loader": "^0.8.0",
|
||||
"jquery": "^3.4.1",
|
||||
"jquery": "^3.5.0",
|
||||
"jquery-mask-plugin": "^1.14.16",
|
||||
"jquery-serializejson": "^2.9.0",
|
||||
"marionette.approuter": "^1.0.2",
|
||||
|
2492
frontend/yarn.lock
2492
frontend/yarn.lock
File diff suppressed because it is too large
Load Diff
251
global/certbot-dns-plugins.js
Normal file
251
global/certbot-dns-plugins.js
Normal file
@ -0,0 +1,251 @@
|
||||
/**
|
||||
* This file contains info about available Certbot DNS plugins.
|
||||
* This only works for plugins which use the standard argument structure, so:
|
||||
* --authenticator <plugin-name> --<plugin-name>-credentials <FILE> --<plugin-name>-propagation-seconds <number>
|
||||
*
|
||||
* File Structure:
|
||||
*
|
||||
* {
|
||||
* cloudflare: {
|
||||
* display_name: "Name displayed to the user",
|
||||
* package_name: "Package name in PyPi repo",
|
||||
* package_version: "Package version in PyPi repo",
|
||||
* credentials: `Template of the credentials file`,
|
||||
* full_plugin_name: "The full plugin name as used in the commandline with certbot, including prefixes, e.g. 'certbot-dns-njalla:dns-njalla'",
|
||||
* credentials_file: Whether the plugin has a credentials file
|
||||
* },
|
||||
* ...
|
||||
* }
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
cloudflare: {
|
||||
display_name: 'Cloudflare',
|
||||
package_name: 'certbot-dns-cloudflare',
|
||||
package_version: '1.8.0',
|
||||
credentials: `# Cloudflare API token
|
||||
dns_cloudflare_api_token = 0123456789abcdef0123456789abcdef01234567`,
|
||||
full_plugin_name: 'dns-cloudflare',
|
||||
},
|
||||
//####################################################//
|
||||
cloudxns: {
|
||||
display_name: 'CloudXNS',
|
||||
package_name: 'certbot-dns-cloudxns',
|
||||
package_version: '1.8.0',
|
||||
credentials: `dns_cloudxns_api_key = 1234567890abcdef1234567890abcdef
|
||||
dns_cloudxns_secret_key = 1122334455667788`,
|
||||
full_plugin_name: 'dns-cloudxns',
|
||||
},
|
||||
//####################################################//
|
||||
corenetworks: {
|
||||
display_name: 'Core Networks',
|
||||
package_name: 'certbot-dns-corenetworks',
|
||||
package_version: '0.1.4',
|
||||
credentials: `certbot_dns_corenetworks:dns_corenetworks_username = asaHB12r
|
||||
certbot_dns_corenetworks:dns_corenetworks_password = secure_password`,
|
||||
full_plugin_name: 'certbot-dns-corenetworks:dns-corenetworks',
|
||||
},
|
||||
//####################################################//
|
||||
cpanel: {
|
||||
display_name: 'cPanel',
|
||||
package_name: 'certbot-dns-cpanel',
|
||||
package_version: '0.2.2',
|
||||
credentials: `certbot_dns_cpanel:cpanel_url = https://cpanel.example.com:2083
|
||||
certbot_dns_cpanel:cpanel_username = user
|
||||
certbot_dns_cpanel:cpanel_password = hunter2`,
|
||||
full_plugin_name: 'certbot-dns-cpanel:cpanel',
|
||||
},
|
||||
//####################################################//
|
||||
digitalocean: {
|
||||
display_name: 'DigitalOcean',
|
||||
package_name: 'certbot-dns-digitalocean',
|
||||
package_version: '1.8.0',
|
||||
credentials: 'dns_digitalocean_token = 0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff',
|
||||
full_plugin_name: 'dns-digitalocean',
|
||||
},
|
||||
//####################################################//
|
||||
directadmin: {
|
||||
display_name: 'DirectAdmin',
|
||||
package_name: 'certbot-dns-directadmin',
|
||||
package_version: '0.0.20',
|
||||
credentials: `directadmin_url = https://my.directadminserver.com:2222
|
||||
directadmin_username = username
|
||||
directadmin_password = aSuperStrongPassword`,
|
||||
full_plugin_name: 'certbot-dns-directadmin:directadmin',
|
||||
},
|
||||
//####################################################//
|
||||
dnsimple: {
|
||||
display_name: 'DNSimple',
|
||||
package_name: 'certbot-dns-dnsimple',
|
||||
package_version: '1.8.0',
|
||||
credentials: 'dns_dnsimple_token = MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw',
|
||||
full_plugin_name: 'dns-dnsimple',
|
||||
},
|
||||
//####################################################//
|
||||
dnsmadeeasy: {
|
||||
display_name: 'DNS Made Easy',
|
||||
package_name: 'certbot-dns-dnsmadeeasy',
|
||||
package_version: '1.8.0',
|
||||
credentials: `dns_dnsmadeeasy_api_key = 1c1a3c91-4770-4ce7-96f4-54c0eb0e457a
|
||||
dns_dnsmadeeasy_secret_key = c9b5625f-9834-4ff8-baba-4ed5f32cae55`,
|
||||
full_plugin_name: 'dns-dnsmadeeasy',
|
||||
},
|
||||
//####################################################//
|
||||
dnspod: {
|
||||
display_name: 'DNSPod',
|
||||
package_name: 'certbot-dns-dnspod',
|
||||
package_version: '0.1.0',
|
||||
credentials: `certbot_dns_dnspod:dns_dnspod_email = "DNSPOD-API-REQUIRES-A-VALID-EMAIL"
|
||||
certbot_dns_dnspod:dns_dnspod_api_token = "DNSPOD-API-TOKEN"`,
|
||||
full_plugin_name: 'certbot-dns-dnspod:dns-dnspod',
|
||||
},
|
||||
//####################################################//
|
||||
google: {
|
||||
display_name: 'Google',
|
||||
package_name: 'certbot-dns-google',
|
||||
package_version: '1.8.0',
|
||||
credentials: `{
|
||||
"type": "service_account",
|
||||
...
|
||||
}`,
|
||||
full_plugin_name: 'dns-google',
|
||||
},
|
||||
//####################################################//
|
||||
hetzner: {
|
||||
display_name: 'Hetzner',
|
||||
package_name: 'certbot-dns-hetzner',
|
||||
package_version: '1.0.4',
|
||||
credentials: 'certbot_dns_hetzner:dns_hetzner_api_token = 0123456789abcdef0123456789abcdef',
|
||||
full_plugin_name: 'certbot-dns-hetzner:dns-hetzner',
|
||||
},
|
||||
//####################################################//
|
||||
inwx: {
|
||||
display_name: 'INWX',
|
||||
package_name: 'certbot-dns-inwx',
|
||||
package_version: '2.1.2',
|
||||
credentials: `certbot_dns_inwx:dns_inwx_url = https://api.domrobot.com/xmlrpc/
|
||||
certbot_dns_inwx:dns_inwx_username = your_username
|
||||
certbot_dns_inwx:dns_inwx_password = your_password
|
||||
certbot_dns_inwx:dns_inwx_shared_secret = your_shared_secret optional`,
|
||||
full_plugin_name: 'certbot-dns-inwx:dns-inwx',
|
||||
},
|
||||
//####################################################//
|
||||
ispconfig: {
|
||||
display_name: 'ISPConfig',
|
||||
package_name: 'certbot-dns-ispconfig',
|
||||
package_version: '0.2.0',
|
||||
credentials: `certbot_dns_ispconfig:dns_ispconfig_username = myremoteuser
|
||||
certbot_dns_ispconfig:dns_ispconfig_password = verysecureremoteuserpassword
|
||||
certbot_dns_ispconfig:dns_ispconfig_endpoint = https://localhost:8080`,
|
||||
full_plugin_name: 'certbot-dns-ispconfig:dns-ispconfig',
|
||||
},
|
||||
//####################################################//
|
||||
isset: {
|
||||
display_name: 'Isset',
|
||||
package_name: 'certbot-dns-isset',
|
||||
package_version: '0.0.3',
|
||||
credentials: `certbot_dns_isset:dns_isset_endpoint="https://customer.isset.net/api"
|
||||
certbot_dns_isset:dns_isset_token="<token>"`,
|
||||
full_plugin_name: 'certbot-dns-isset:dns-isset',
|
||||
},
|
||||
//####################################################//
|
||||
linode: {
|
||||
display_name: 'Linode',
|
||||
package_name: 'certbot-dns-linode',
|
||||
package_version: '1.8.0',
|
||||
credentials: `dns_linode_key = 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ64
|
||||
dns_linode_version = [<blank>|3|4]`,
|
||||
full_plugin_name: 'dns-linode',
|
||||
},
|
||||
//####################################################//
|
||||
luadns: {
|
||||
display_name: 'LuaDNS',
|
||||
package_name: 'certbot-dns-luadns',
|
||||
package_version: '1.8.0',
|
||||
credentials: `dns_luadns_email = user@example.com
|
||||
dns_luadns_token = 0123456789abcdef0123456789abcdef`,
|
||||
full_plugin_name: 'dns-luadns',
|
||||
},
|
||||
//####################################################//
|
||||
netcup: {
|
||||
display_name: 'netcup',
|
||||
package_name: 'certbot-dns-netcup',
|
||||
package_version: '1.0.0',
|
||||
credentials: `dns_netcup_customer_id = 123456
|
||||
dns_netcup_api_key = 0123456789abcdef0123456789abcdef01234567
|
||||
dns_netcup_api_password = abcdef0123456789abcdef01234567abcdef0123`,
|
||||
full_plugin_name: 'certbot-dns-netcup:dns-netcup',
|
||||
},
|
||||
//####################################################//
|
||||
njalla: {
|
||||
display_name: 'Njalla',
|
||||
package_name: 'certbot-dns-njalla',
|
||||
package_version: '0.0.4',
|
||||
credentials: 'certbot_dns_njalla:dns_njalla_token = 0123456789abcdef0123456789abcdef01234567',
|
||||
full_plugin_name: 'certbot-dns-njalla:dns-njalla',
|
||||
},
|
||||
//####################################################//
|
||||
nsone: {
|
||||
display_name: 'NS1',
|
||||
package_name: 'certbot-dns-nsone',
|
||||
package_version: '1.8.0',
|
||||
credentials: 'dns_nsone_api_key = MDAwMDAwMDAwMDAwMDAw',
|
||||
full_plugin_name: 'dns-nsone',
|
||||
},
|
||||
//####################################################//
|
||||
ovh: {
|
||||
display_name: 'OVH',
|
||||
package_name: 'certbot-dns-ovh',
|
||||
package_version: '1.8.0',
|
||||
credentials: `dns_ovh_endpoint = ovh-eu
|
||||
dns_ovh_application_key = MDAwMDAwMDAwMDAw
|
||||
dns_ovh_application_secret = MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw
|
||||
dns_ovh_consumer_key = MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw`,
|
||||
full_plugin_name: 'dns-ovh',
|
||||
},
|
||||
//####################################################//
|
||||
powerdns: {
|
||||
display_name: 'PowerDNS',
|
||||
package_name: 'certbot-dns-powerdns',
|
||||
package_version: '0.2.0',
|
||||
credentials: `certbot_dns_powerdns:dns_powerdns_api_url = https://api.mypowerdns.example.org
|
||||
certbot_dns_powerdns:dns_powerdns_api_key = AbCbASsd!@34`,
|
||||
full_plugin_name: 'certbot-dns-powerdns:dns-powerdns',
|
||||
},
|
||||
//####################################################//
|
||||
rfc2136: {
|
||||
display_name: 'RFC 2136',
|
||||
package_name: 'certbot-dns-rfc2136',
|
||||
package_version: '1.8.0',
|
||||
credentials: `# Target DNS server
|
||||
dns_rfc2136_server = 192.0.2.1
|
||||
# Target DNS port
|
||||
dns_rfc2136_port = 53
|
||||
# TSIG key name
|
||||
dns_rfc2136_name = keyname.
|
||||
# TSIG key secret
|
||||
dns_rfc2136_secret = 4q4wM/2I180UXoMyN4INVhJNi8V9BCV+jMw2mXgZw/CSuxUT8C7NKKFs AmKd7ak51vWKgSl12ib86oQRPkpDjg==
|
||||
# TSIG key algorithm
|
||||
dns_rfc2136_algorithm = HMAC-SHA512`,
|
||||
full_plugin_name: 'dns-rfc2136',
|
||||
},
|
||||
//####################################################//
|
||||
route53: {
|
||||
display_name: 'Route 53 (Amazon)',
|
||||
package_name: 'certbot-dns-route53',
|
||||
package_version: '1.8.0',
|
||||
credentials: `[default]
|
||||
aws_access_key_id=AKIAIOSFODNN7EXAMPLE
|
||||
aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY`,
|
||||
full_plugin_name: 'dns-route53',
|
||||
},
|
||||
//####################################################//
|
||||
vultr: {
|
||||
display_name: 'Vultr',
|
||||
package_name: 'certbot-dns-vultr',
|
||||
package_version: '1.0.3',
|
||||
credentials: 'certbot_dns_vultr:dns_vultr_key = YOUR_VULTR_API_KEY',
|
||||
full_plugin_name: 'certbot-dns-vultr:dns-vultr',
|
||||
},
|
||||
};
|
17
scripts/.common.sh
Normal file
17
scripts/.common.sh
Normal file
@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Colors
|
||||
BLUE='\E[1;34m'
|
||||
CYAN='\E[1;36m'
|
||||
GREEN='\E[1;32m'
|
||||
RED='\E[1;31m'
|
||||
RESET='\E[0m'
|
||||
YELLOW='\E[1;33m'
|
||||
|
||||
export BLUE CYAN GREEN RED RESET YELLOW
|
||||
|
||||
# Docker Compose
|
||||
COMPOSE_PROJECT_NAME="npmdev"
|
||||
COMPOSE_FILE="docker/docker-compose.dev.yml"
|
||||
|
||||
export COMPOSE_FILE COMPOSE_PROJECT_NAME
|
@ -1,10 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
CYAN='\E[1;36m'
|
||||
YELLOW='\E[1;33m'
|
||||
BLUE='\E[1;34m'
|
||||
GREEN='\E[1;32m'
|
||||
RESET='\E[0m'
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
. "$DIR/.common.sh"
|
||||
|
||||
echo -e "${BLUE}❯ ${CYAN}Building docker multiarch: ${YELLOW}${*}${RESET}"
|
||||
|
||||
|
@ -1,15 +1,7 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
CYAN='\E[1;36m'
|
||||
BLUE='\E[1;34m'
|
||||
RED='\E[1;31m'
|
||||
RESET='\E[0m'
|
||||
|
||||
COMPOSE_PROJECT_NAME="npmdev"
|
||||
COMPOSE_FILE="docker/docker-compose.dev.yml"
|
||||
export COMPOSE_FILE COMPOSE_PROJECT_NAME
|
||||
. "$DIR/.common.sh"
|
||||
|
||||
# Ensure docker-compose exists
|
||||
# Make sure docker exists
|
||||
|
@ -1,12 +1,7 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
CYAN='\E[1;36m'
|
||||
BLUE='\E[1;34m'
|
||||
RED='\E[1;31m'
|
||||
GREEN='\E[1;32m'
|
||||
RESET='\E[0m'
|
||||
. "$DIR/.common.sh"
|
||||
|
||||
# Ensure docker-compose exists
|
||||
if hash docker 2>/dev/null; then
|
||||
|
@ -2,11 +2,8 @@
|
||||
|
||||
# Note: This script is designed to be run inside CI builds
|
||||
|
||||
CYAN='\E[1;36m'
|
||||
YELLOW='\E[1;33m'
|
||||
BLUE='\E[1;34m'
|
||||
GREEN='\E[1;32m'
|
||||
RESET='\E[0m'
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
. "$DIR/.common.sh"
|
||||
|
||||
echo -e "${BLUE}❯ ${CYAN}Uploading docs in: ${YELLOW}$1${RESET}"
|
||||
|
||||
|
@ -1,20 +1,16 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
CYAN='\E[1;36m'
|
||||
BLUE='\E[1;34m'
|
||||
RED='\E[1;31m'
|
||||
GREEN='\E[1;32m'
|
||||
RESET='\E[0m'
|
||||
. "$DIR/.common.sh"
|
||||
|
||||
DOCKER_IMAGE=jc21/alpine-nginx-full:node
|
||||
|
||||
# Ensure docker exists
|
||||
if hash docker 2>/dev/null; then
|
||||
docker pull "${DOCKER_IMAGE}"
|
||||
cd "${DIR}/.."
|
||||
echo -e "${BLUE}❯ ${CYAN}Building Frontend ...${RESET}"
|
||||
docker run --rm -e CI=true -v "$(pwd)/frontend:/app/frontend" -w /app/frontend "$DOCKER_IMAGE" sh -c "yarn install && yarn build && yarn build && chown -R $(id -u):$(id -g) /app/frontend"
|
||||
docker run --rm -e CI=true -v "$(pwd)/frontend:/app/frontend" -v "$(pwd)/global:/app/global" -w /app/frontend "$DOCKER_IMAGE" sh -c "yarn install && yarn build && yarn build && chown -R $(id -u):$(id -g) /app/frontend"
|
||||
echo -e "${BLUE}❯ ${GREEN}Building Frontend Complete${RESET}"
|
||||
else
|
||||
echo -e "${RED}❯ docker command is not available${RESET}"
|
||||
|
@ -1,16 +1,7 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
CYAN='\E[1;36m'
|
||||
BLUE='\E[1;34m'
|
||||
YELLOW='\E[1;33m'
|
||||
RED='\E[1;31m'
|
||||
RESET='\E[0m'
|
||||
|
||||
COMPOSE_PROJECT_NAME="npmdev"
|
||||
COMPOSE_FILE="docker/docker-compose.dev.yml"
|
||||
export COMPOSE_FILE COMPOSE_PROJECT_NAME
|
||||
. "$DIR/.common.sh"
|
||||
|
||||
# Ensure docker-compose exists
|
||||
if hash docker-compose 2>/dev/null; then
|
||||
|
@ -1,15 +1,7 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
CYAN='\E[1;36m'
|
||||
BLUE='\E[1;34m'
|
||||
RED='\E[1;31m'
|
||||
RESET='\E[0m'
|
||||
|
||||
COMPOSE_PROJECT_NAME="npmdev"
|
||||
COMPOSE_FILE="docker/docker-compose.dev.yml"
|
||||
export COMPOSE_FILE COMPOSE_PROJECT_NAME
|
||||
. "$DIR/.common.sh"
|
||||
|
||||
# Ensure docker-compose exists
|
||||
# Make sure docker exists
|
||||
|
@ -1,21 +1,13 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
CYAN='\E[1;36m'
|
||||
BLUE='\E[1;34m'
|
||||
RED='\E[1;31m'
|
||||
RESET='\E[0m'
|
||||
|
||||
COMPOSE_PROJECT_NAME="npmdev"
|
||||
COMPOSE_FILE="docker/docker-compose.dev.yml"
|
||||
export COMPOSE_FILE COMPOSE_PROJECT_NAME
|
||||
. "$DIR/.common.sh"
|
||||
|
||||
# Ensure docker-compose exists
|
||||
if hash docker-compose 2>/dev/null; then
|
||||
cd "${DIR}/.."
|
||||
echo -e "${BLUE}❯ ${CYAN}Testing Dev Stack ...${RESET}"
|
||||
docker-compose exec -T npm bash -c "cd /app/backend && task test"
|
||||
docker-compose exec -T npm bash -c "cd /app && task test"
|
||||
else
|
||||
echo -e "${RED}❯ docker-compose command is not available${RESET}"
|
||||
fi
|
||||
|
@ -1,11 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
CYAN='\E[1;36m'
|
||||
YELLOW='\E[1;33m'
|
||||
BLUE='\E[1;34m'
|
||||
GREEN='\E[1;32m'
|
||||
RED='\E[1;31m'
|
||||
RESET='\E[0m'
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
. "$DIR/.common.sh"
|
||||
|
||||
if [ "$1" == "" ]; then
|
||||
echo "Waits for a docker container to be healthy."
|
||||
|
3
test/.gitignore
vendored
3
test/.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
.vscode
|
||||
node_modules
|
||||
|
||||
results
|
||||
cypress/videos
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM cypress/included:4.0.2
|
||||
FROM cypress/included:4.12.1
|
||||
|
||||
COPY --chown=1000 ./test /test
|
||||
|
||||
|
@ -1,15 +1,12 @@
|
||||
{
|
||||
"requestTimeout": 30000,
|
||||
"defaultCommandTimeout": 20000,
|
||||
"reporter": "mocha-junit-reporter",
|
||||
"reporter": "cypress-multi-reporters",
|
||||
"reporterOptions": {
|
||||
"jenkinsMode": true,
|
||||
"rootSuiteTitle": "Cypress",
|
||||
"jenkinsClassnamePrefix": "Cypress.",
|
||||
"mochaFile": "/results/junit/my-test-output-[hash].xml"
|
||||
"configFile": "multi-reporter.json"
|
||||
},
|
||||
"videosFolder": "/results/videos",
|
||||
"screenshotsFolder": "/results/screenshots",
|
||||
"videosFolder": "results/videos",
|
||||
"screenshotsFolder": "results/screenshots",
|
||||
"env": {
|
||||
"swaggerBase": "{{baseUrl}}/api/schema",
|
||||
"RETRIES": 4
|
||||
|
@ -1,13 +1,14 @@
|
||||
{
|
||||
"requestTimeout": 30000,
|
||||
"defaultCommandTimeout": 20000,
|
||||
"reporter": "junit",
|
||||
"reporter": "cypress-multi-reporters",
|
||||
"reporterOptions": {
|
||||
"mochaFile": "results/junit/my-test-output-[hash].xml"
|
||||
"configFile": "multi-reporter.json"
|
||||
},
|
||||
"video": false,
|
||||
"screenshotsFolder": "cypress/results/screenshots",
|
||||
"videos": false,
|
||||
"screenshotsFolder": "results/screenshots",
|
||||
"env": {
|
||||
"swaggerBase": "{{baseUrl}}/api/schema"
|
||||
"swaggerBase": "{{baseUrl}}/api/schema",
|
||||
"RETRIES": 0
|
||||
}
|
||||
}
|
||||
|
@ -2,17 +2,15 @@
|
||||
|
||||
describe('Basic API checks', () => {
|
||||
it('Should return a valid health payload', function () {
|
||||
cy.wait(2000);
|
||||
cy.task('backendApiGet', {
|
||||
path: '/api/',
|
||||
}).then((data) => {
|
||||
// Check the swagger schema:
|
||||
cy.validateSwaggerSchema('get', '/', data);
|
||||
cy.validateSwaggerSchema('get', 200, '/', data);
|
||||
});
|
||||
});
|
||||
|
||||
it('Should return a valid schema payload', function () {
|
||||
cy.wait(2000);
|
||||
cy.task('backendApiGet', {
|
||||
path: '/api/schema',
|
||||
}).then((data) => {
|
48
test/cypress/integration/api/Users.spec.js
Normal file
48
test/cypress/integration/api/Users.spec.js
Normal file
@ -0,0 +1,48 @@
|
||||
/// <reference types="Cypress" />
|
||||
|
||||
describe('Users endpoints', () => {
|
||||
let token;
|
||||
|
||||
before(() => {
|
||||
cy.getToken().then((tok) => {
|
||||
token = tok;
|
||||
});
|
||||
});
|
||||
|
||||
it('Should be able to get yourself', function() {
|
||||
cy.task('backendApiGet', {
|
||||
token: token,
|
||||
path: '/api/users/me'
|
||||
}).then((data) => {
|
||||
cy.validateSwaggerSchema('get', 200, '/users/{userID}', data);
|
||||
expect(data).to.have.property('id');
|
||||
expect(data.id).to.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('Should be able to get all users', function() {
|
||||
cy.task('backendApiGet', {
|
||||
token: token,
|
||||
path: '/api/users'
|
||||
}).then((data) => {
|
||||
cy.validateSwaggerSchema('get', 200, '/users', data);
|
||||
expect(data.length).to.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('Should be able to update yourself', function() {
|
||||
cy.task('backendApiPut', {
|
||||
token: token,
|
||||
path: '/api/users/me',
|
||||
data: {
|
||||
name: 'changed name'
|
||||
}
|
||||
}).then((data) => {
|
||||
cy.validateSwaggerSchema('put', 200, '/users/{userID}', data);
|
||||
expect(data).to.have.property('id');
|
||||
expect(data.id).to.be.greaterThan(0);
|
||||
expect(data.name).to.be.equal('changed name');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
@ -13,82 +13,30 @@
|
||||
* Check the swagger schema:
|
||||
*
|
||||
* @param {string} method API Method in swagger doc, "get", "put", "post", "delete"
|
||||
* @param {number} statusCode API status code in swagger doc
|
||||
* @param {string} path Swagger doc endpoint path, exactly as defined in swagger doc
|
||||
* @param {*} data The API response data to check against the swagger schema
|
||||
*/
|
||||
Cypress.Commands.add('validateSwaggerSchema', (method, path, data) => {
|
||||
Cypress.Commands.add('validateSwaggerSchema', (method, statusCode, path, data) => {
|
||||
cy.task('validateSwaggerSchema', {
|
||||
file: Cypress.env('swaggerBase'),
|
||||
endpoint: path,
|
||||
method: method,
|
||||
statusCode: 200,
|
||||
statusCode: statusCode,
|
||||
responseSchema: data,
|
||||
verbose: true
|
||||
}).should('equal', null);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('getToken', () => {
|
||||
cy.task('backendApiGet', {
|
||||
path: '/api/',
|
||||
}).then((data) => {
|
||||
// Check the swagger schema:
|
||||
cy.task('validateSwaggerSchema', {
|
||||
endpoint: '/',
|
||||
method: 'get',
|
||||
statusCode: 200,
|
||||
responseSchema: data,
|
||||
verbose: true,
|
||||
}).should('equal', null);
|
||||
|
||||
if (!data.result.setup) {
|
||||
cy.log('Setup = false');
|
||||
// create a new user
|
||||
cy.createInitialUser().then(() => {
|
||||
return cy.getToken();
|
||||
});
|
||||
} else {
|
||||
cy.log('Setup = true');
|
||||
// login with existing user
|
||||
cy.task('backendApiPost', {
|
||||
path: '/api/tokens',
|
||||
data: {
|
||||
type: 'password',
|
||||
identity: 'jc@jc21.com',
|
||||
secret: 'changeme'
|
||||
}
|
||||
}).then(res => {
|
||||
cy.wrap(res.result.token);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('createInitialUser', () => {
|
||||
return cy.task('backendApiPost', {
|
||||
path: '/api/users',
|
||||
// login with existing user
|
||||
cy.task('backendApiPost', {
|
||||
path: '/api/tokens',
|
||||
data: {
|
||||
name: 'Jamie Curnow',
|
||||
nickname: 'James',
|
||||
email: 'jc@jc21.com',
|
||||
roles: [],
|
||||
is_disabled: false,
|
||||
auth: {
|
||||
type: 'password',
|
||||
secret: 'changeme'
|
||||
}
|
||||
identity: 'admin@example.com',
|
||||
secret: 'changeme'
|
||||
}
|
||||
}).then((data) => {
|
||||
// Check the swagger schema:
|
||||
cy.task('validateSwaggerSchema', {
|
||||
endpoint: '/users',
|
||||
method: 'post',
|
||||
statusCode: 201,
|
||||
responseSchema: data,
|
||||
verbose: true
|
||||
}).should('equal', null);
|
||||
|
||||
expect(data.result).to.have.property('id');
|
||||
expect(data.result.id).to.be.greaterThan(0);
|
||||
cy.wrap(data.result);
|
||||
}).then(res => {
|
||||
cy.wrap(res.token);
|
||||
});
|
||||
});
|
||||
|
9
test/multi-reporter.json
Normal file
9
test/multi-reporter.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"reporterEnabled": "spec, mocha-junit-reporter",
|
||||
"mochaJunitReporterReporterOptions": {
|
||||
"jenkinsMode": true,
|
||||
"rootSuiteTitle": "Cypress.npm",
|
||||
"jenkinsClassnamePrefix": "Cypress.npm.",
|
||||
"mochaFile": "results/junit/cypress.npm.[hash].xml"
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user