Compare commits

...

115 Commits

Author SHA1 Message Date
Jamie Curnow
5e66d677f1
Adds test for dashboard endpoints
All checks were successful
Close stale issues and PRs / stale (push) Successful in 18s
2025-03-24 14:34:45 +10:00
jc21
79d28f03d0
Merge pull request #4346 from Sander0542/feature/security-schemes-component
All checks were successful
Close stale issues and PRs / stale (push) Successful in 4s
API Schema Improvements
2025-02-07 12:39:49 +10:00
Jamie Curnow
c4df89df1f
Fix dashboard loading loop and freezing the page 2025-02-06 13:38:47 +10:00
jc21
34c703f8b4
Merge branch 'master' into develop 2025-02-06 08:52:55 +10:00
Jamie Curnow
0a05d8f0ad
Bump version 2025-02-06 08:39:03 +10:00
jc21
0a9141fad5
Merge pull request #4208 from badkeyy/feature/add-zone-edit-certbot-plugin
Add ZoneEdit certbot plugin
2025-02-06 08:33:11 +10:00
jc21
42836774b7
Merge branch 'develop' into feature/add-zone-edit-certbot-plugin 2025-02-06 08:33:01 +10:00
jc21
2a07544f58
Merge pull request #4235 from FabianK3/update-domainoffensive-certbot-plugin
Update DomainOffensive certbot plugin
2025-02-06 08:30:09 +10:00
jc21
dc9d884743
Merge pull request #4292 from icaksh/patch-1
feat: change htpasswd to openssl
2025-02-06 08:29:15 +10:00
jc21
0d5d2b1b7c
Merge pull request #4283 from badkeyy/feature/show-active-host-in-cert-list
SSL Certificates: Show if cert is in use on host
2025-02-06 07:43:12 +10:00
Sander Jochems
df48b835c4
Update order to match others 2025-02-05 22:20:21 +01:00
Sander Jochems
8a1557154a
Add certificate fields to boolFields 2025-02-05 22:15:12 +01:00
Sander Jochems
a6af5ec2c7
Remove certificate as required from proxy host 2025-02-05 18:18:50 +01:00
Sander Jochems
14d7c35fd7
Fix whitespaces 2025-02-05 17:31:09 +01:00
Sander Jochems
cfcf78aaee
Set bearer auth security component 2025-02-05 17:29:40 +01:00
jc21
3a01b2c84f
Merge pull request #4334 from nwagenmakers/mijn-host-patch
All checks were successful
Close stale issues and PRs / stale (push) Successful in 4s
Update certbot-dns-plugins.json (mijn-host)
2025-02-05 20:36:06 +10:00
jc21
e1c84a5c10
Merge pull request #4338 from Sander0542/fix/token-expires-type
Fix type for token.expires
2025-02-05 20:35:33 +10:00
jc21
c56c95a59a
Merge pull request #4344 from NginxProxyManager/stream-ssl
SSL for Streams - 2025
2025-02-05 18:22:51 +10:00
Jamie Curnow
6a60627833
Cypress test for Streams
and updated cypress + packages
2025-02-05 16:02:17 +10:00
Jamie Curnow
b4793d3c16
Adds testssl.sh and mkcert to cypress stack 2025-02-05 08:10:11 +10:00
Jamie Curnow
68a7803513
Fix api schema after merging latest changes 2025-02-04 17:55:28 +10:00
jbowring
2657af97cf
Fix stream update not persisting 2025-02-04 17:14:07 +10:00
jbowring
4452f014b9
Fix whitespace in nginx stream config 2025-02-04 17:14:07 +10:00
jbowring
cd80cc8e4d
Add certificate to streams database model 2025-02-04 17:14:04 +10:00
jbowring
ee4250d770
Add SSL column to streams table UI 2025-02-04 17:12:05 +10:00
jbowring
3dbc70faa6
Add SSL tab to stream UI 2025-02-04 17:12:04 +10:00
jbowring
3091c21cae
Add SSL certificate to TCP streams if certificate in database 2025-02-04 17:12:04 +10:00
Sander Jochems
57cd2a1919
Fix type for token.expires 2025-02-03 21:47:41 +01:00
nwagenmakers
ad5936c530
Update certbot-dns-plugins.json (mijn-host)
Updated credentials hint/text in mijn-host plugin entry
2025-02-01 13:10:53 +01:00
jc21
498109addb
Merge pull request #4310 from NginxProxyManager/dependabot/npm_and_yarn/docs/vite-5.4.14
All checks were successful
Close stale issues and PRs / stale (push) Successful in 3s
Bump vite from 5.4.8 to 5.4.14 in /docs
2025-01-28 18:08:46 +10:00
jc21
3f3aacd7ec
Merge pull request #4274 from Dim145/develop
[Postgres] fix error in access_list get
2025-01-28 14:03:07 +10:00
dependabot[bot]
bb4ecf812d
Bump vite from 5.4.8 to 5.4.14 in /docs
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.4.8 to 5.4.14.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.4.14/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.4.14/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-22 07:09:04 +00:00
Julian Gassner
c05f9695d0
Merge branch 'develop' into feature/add-zone-edit-certbot-plugin 2025-01-15 15:37:53 +01:00
Julian Gassner
6343b398f0 Add --no-deps 2025-01-15 14:36:38 +00:00
icaksh
59362b7477 feat: change htpasswd to openssl 2025-01-12 19:16:38 +07:00
Julian Gassner
aedaaa18e0 Fix whitespace 2025-01-10 05:20:28 +01:00
Julian Gassner
080bd0b749 Added status of certificates to the certificate list and show on which domain names the certificates are in use 2025-01-10 05:15:22 +01:00
Jamie Curnow
9687e9e450
Use previous version of powerdns image, newer version is broken
All checks were successful
Close stale issues and PRs / stale (push) Successful in 3s
2025-01-07 10:30:08 +10:00
Jamie Curnow
5a234bb88c
Fix incorrect test folder in ci results 2025-01-07 08:13:04 +10:00
jc21
4de4b65036
Merge pull request #4252 from GergelyGombai/develop
Add Gcore DNS Provider
2025-01-07 07:54:44 +10:00
dim145
f1c97c7c36 fix: add missing group_by clause for access_list get 2025-01-03 00:39:29 +01:00
jc21
b4f49969d6
Merge pull request #4261 from NginxProxyManager/develop
v2.12.2
2024-12-29 14:40:05 +10:00
jc21
ec12d8f9bf
Merge pull request #4148 from Medan-rfz/develop
Added certbot plugin for Beget DNS service
2024-12-29 14:00:51 +10:00
jc21
e50e3def9d
Merge pull request #4169 from andrew-codechimp/bump-porkbun
Bump certbot-dns-porkbun
2024-12-29 14:00:18 +10:00
jc21
6415f284f9
Merge pull request #4256 from bigcat26/develop
upgrade certbot-dns-aliyun plugin from 0.38.1 to 2.0.0
2024-12-29 13:52:03 +10:00
Chris Xiong
98e5997f0a
upgrade certbot-dns-aliyun plugin from 0.38.1 to 2.0.0 2024-12-26 09:51:28 +08:00
Jamie Curnow
fc30a92bd4
Open port for authentik in dev
All checks were successful
Close stale issues and PRs / stale (push) Successful in 4s
2024-12-24 18:19:52 +10:00
Jamie Curnow
e2011ee45c
Bump version 2024-12-24 17:51:25 +10:00
jc21
1406e75c2c
Merge pull request #4254 from NginxProxyManager/postgres
Postgres
2024-12-24 17:24:05 +10:00
Jamie Curnow
ca3ee98c68
Postgres Support
- Combines #4086 and #4087 PRs
- Adds authentik in CI stack
2024-12-24 16:48:48 +10:00
jc21
f90d839ebe
Merge pull request #4246 from JanzenJohn/develop
Remove infinite requests loop
2024-12-24 08:16:48 +10:00
jc21
be5278f31e
Merge pull request #4247 from miguelangel-nubla/patch-1
Add custom configuration to 404 hosts
2024-12-24 08:15:55 +10:00
ComradeBlin
73110d5e1e Update Gcore apikey format
I managed to mis-write the format in my previous commit
2024-12-22 01:44:52 +01:00
ComradeBlin
356b98bf7e Add Gcore DNS Provider 2024-12-22 01:02:47 +01:00
Miguel Angel Nubla
3eecf7a38b
Add custom configuration to 404 hosts 2024-12-20 01:03:21 +01:00
Miguel Angel Nubla
7f9240dda7
Add custom configuration to dead_host.conf 2024-12-20 00:59:26 +01:00
John Janzen
f537619ffe Revert "Change onRender function to always update the dashboard stats"
This reverts commit d26e8c1d0c44a5fbeb1264f8fe713bdac0f5e703.

This reopens #4204 (which i can't reproduce sadly)

The reverted commit is responsible for an infinite loop of requests to /hosts, which makes buttons unresponsive on the main page
another way to invalidate the cache needs to be found

this infinite requests loop happens on d26e8c1d0c44a5fbeb1264f8fe713bdac0f5e703
and on the docker image
`nginxproxymanager/nginx-proxy-manager-dev:pr-4206`

the docker image is attaced to the pr #4206 which merges the commit
2024-12-19 16:16:03 +01:00
jc21
805968aac6
Merge pull request #4185 from muescha/patch-1
All checks were successful
Close stale issues and PRs / stale (push) Successful in 4s
Update index.md: add link to Proxmox VE Helper-Scripts
2024-12-17 07:59:45 +10:00
jc21
2a4093c1b8
Merge pull request #4215 from TECH7Fox/patch-1
Add hostingnl DNS Challenge provider
2024-12-17 07:57:43 +10:00
jc21
ae2ac8a733
Merge pull request #4230 from NginxProxyManager/dependabot/npm_and_yarn/docs/nanoid-3.3.8
Bump nanoid from 3.3.7 to 3.3.8 in /docs
2024-12-17 07:52:24 +10:00
FabianK3
5d087f1256 Update DomainOffensive certbot plugin 2024-12-15 11:35:58 +01:00
dependabot[bot]
c6eca2578e
Bump nanoid from 3.3.7 to 3.3.8 in /docs
Bumps [nanoid](https://github.com/ai/nanoid) from 3.3.7 to 3.3.8.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.3.7...3.3.8)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-14 10:02:55 +00:00
Jordy Kuhne
56033bee9c
Add hostingnl 2024-12-08 15:23:37 +01:00
Medan-rfz
c6630e87bb Update version 'certbot-beget-plugin' & fix credentials content 2024-12-07 15:01:57 +04:00
Medan
d6b98f51b0
Merge branch 'NginxProxyManager:develop' into develop 2024-12-07 14:27:29 +04:00
Julian Gassner
1e322804ce Add ZoneEdit certbot plugin 2024-12-04 16:47:36 +01:00
jc21
b3de76c945
Merge pull request #4192 from badkeyy/bugfix/fix-user-edit-email-format-check
All checks were successful
Close stale issues and PRs / stale (push) Successful in 4s
Enforce email format when editing user
2024-12-04 14:50:42 +10:00
jc21
fcf4117f8e
Merge pull request #4206 from badkeyy/bugfix/update-dashboard-stats-on-change
Update the dashboard stats every time the dashboard is shown
2024-12-04 13:08:21 +10:00
Julian Gassner
d26e8c1d0c Change onRender function to always update the dashboard stats 2024-12-04 03:45:56 +01:00
Julian Gassner
19ed4c1212 Change click to submit 2024-12-04 03:08:49 +01:00
Julian Gassner
03018d252b
Merge branch 'NginxProxyManager:develop' into bugfix/fix-user-edit-email-format-check 2024-12-04 01:58:08 +01:00
jc21
8351dd41f6
Merge pull request #4199 from NginxProxyManager/dependabot/npm_and_yarn/test/cross-spawn-7.0.6
All checks were successful
Close stale issues and PRs / stale (push) Successful in 4s
Bump cross-spawn from 7.0.3 to 7.0.6 in /test
2024-12-02 10:45:00 +10:00
jc21
97212f2686
Merge pull request #4123 from NginxProxyManager/dependabot/npm_and_yarn/frontend/elliptic-6.6.0
Bump elliptic from 6.5.7 to 6.6.0 in /frontend
2024-12-02 10:44:20 +10:00
dependabot[bot]
fe068a8b51
Bump cross-spawn from 7.0.3 to 7.0.6 in /test
Bumps [cross-spawn](https://github.com/moxystudio/node-cross-spawn) from 7.0.3 to 7.0.6.
- [Changelog](https://github.com/moxystudio/node-cross-spawn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/moxystudio/node-cross-spawn/compare/v7.0.3...v7.0.6)

---
updated-dependencies:
- dependency-name: cross-spawn
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-01 22:49:09 +00:00
jc21
61e2bde98f
Merge pull request #4184 from NginxProxyManager/dependabot/npm_and_yarn/backend/cross-spawn-7.0.6
Bump cross-spawn from 7.0.3 to 7.0.6 in /backend
2024-12-02 08:48:08 +10:00
Julian Gassner
81c9038929 Refactor user form structure 2024-11-27 18:27:11 +01:00
jc21
4ea50ca40c
Merge pull request #4126 from jonasrdl/remove-deprecated-version-line
All checks were successful
Close stale issues and PRs / stale (push) Successful in 4s
docs(setup): Remove deprecated version from docker-compose.yml
2024-11-26 07:37:41 +10:00
jc21
53ed12bcf2
Merge pull request #4163 from Jasparigus/stream_error_correction
Fix Container Bootloop if Stream is used for http/https ports
2024-11-26 07:37:14 +10:00
jc21
cb3e4ed59c
Merge pull request #4137 from irexyc/add-woff2-asset
Add woff2 format to assets.conf for Cache Assets
2024-11-26 07:35:57 +10:00
jc21
b20dc5eade
Merge pull request #4167 from NginxProxyManager/dependabot/npm_and_yarn/test/eslint/plugin-kit-0.2.3
Bump @eslint/plugin-kit from 0.2.0 to 0.2.3 in /test
2024-11-26 07:35:10 +10:00
jc21
586afc0c91
Merge pull request #4187 from kerstenremco/avatar
Fix entries of a deleted user break the UI
2024-11-26 07:31:03 +10:00
Remco Kersten
93ea17a9bb Fix entries of a deleted user break the UI 2024-11-25 20:37:49 +01:00
Muescha
151160a834
Update index.md: add link to Proxmox VE Helper-Scripts
Update index.md: add link to Proxmox VE Helper-Scripts
2024-11-24 20:10:17 +01:00
dependabot[bot]
2075f98cad
Bump cross-spawn from 7.0.3 to 7.0.6 in /backend
Bumps [cross-spawn](https://github.com/moxystudio/node-cross-spawn) from 7.0.3 to 7.0.6.
- [Changelog](https://github.com/moxystudio/node-cross-spawn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/moxystudio/node-cross-spawn/compare/v7.0.3...v7.0.6)

---
updated-dependencies:
- dependency-name: cross-spawn
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-24 03:36:44 +00:00
jc21
07a4e5791f
Merge pull request #4179 from tametsi/develop
All checks were successful
Close stale issues and PRs / stale (push) Successful in 4s
Return generic auth error to prevent user enumeration attacks
2024-11-23 22:39:37 +10:00
tametsi
640a1eeb68
Return generic auth error to prevent user enumeration attacks
On invalid user/password error the error message "Invalid email or password" is returned.
Thereby, no information about the existence of the user is given.
2024-11-22 10:37:09 +01:00
Andrew Jackson
126d3d44ca
Bump certbot-dns-porkbun 2024-11-17 10:44:29 +00:00
dependabot[bot]
20646e7bb5
Bump @eslint/plugin-kit from 0.2.0 to 0.2.3 in /test
Bumps [@eslint/plugin-kit](https://github.com/eslint/rewrite) from 0.2.0 to 0.2.3.
- [Release notes](https://github.com/eslint/rewrite/releases)
- [Changelog](https://github.com/eslint/rewrite/blob/main/release-please-config.json)
- [Commits](https://github.com/eslint/rewrite/compare/core-v0.2.0...plugin-kit-v0.2.3)

---
updated-dependencies:
- dependency-name: "@eslint/plugin-kit"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-15 21:19:05 +00:00
Jasper Stubbe
87998a03ce Fix bootloop if stream is used for http/https port 2024-11-14 11:39:48 -08:00
Medan-rfz
a56342c76a Fix credentials 2024-11-10 19:23:28 +04:00
Medan-rfz
4c89379671 Update version 'certbot-beget-plugin' 2024-11-10 18:31:07 +04:00
Medan-rfz
10b9a49274 Update version 'certbot-beget-plugin' 2024-11-10 16:16:45 +04:00
Medan-rfz
595a742c40 Change beget plugin 2024-11-10 15:09:41 +04:00
Medan-rfz
c171752137 Added certbot plugin for Beget DNS service 2024-11-08 02:29:38 +04:00
irexyc
a0b26b9e98 Add woff2 format to assets.conf for Cache Assets 2024-11-04 20:01:39 +08:00
Jonas Riedel
d6791f4e38 docs(setup): Remove deprecated version from docker-compose.yml 2024-10-31 11:25:38 +01:00
dependabot[bot]
62c94f3099
Bump elliptic from 6.5.7 to 6.6.0 in /frontend
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.7 to 6.6.0.
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.7...v6.6.0)

---
updated-dependencies:
- dependency-name: elliptic
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-31 02:19:58 +00:00
jc21
25a26d6175
Merge pull request #4112 from prospo/develop
All checks were successful
Close stale issues and PRs / stale (push) Successful in 4s
feat: Add leaseweb to certbot-dns-plugins
2024-10-30 14:40:20 +10:00
jc21
17246e418f
Merge pull request #4118 from mitossoft-rd/patch-1
Remove variable usage from proxy_pass directive to fix resolution issues
2024-10-30 14:39:48 +10:00
mitossoft-rd
f7d3ca0b07
Cleaning unused variable. 2024-10-28 15:18:54 +03:00
mitossoft-rd
a55de386e7
Fix URL format 2024-10-28 15:15:08 +03:00
mitossoft-rd
e9d4f5b827
Remove variable usage from proxy_pass directive to fix resolution issues
By using a static URL, the backend server can be accessed reliably, avoiding the common 404 errors or "no resolver defined" issues seen when variables are used.
2024-10-28 02:59:23 +03:00
Emil
1c1cee3836 feat: Add leaseweb to certbot-dns-plugins 2024-10-25 13:25:09 +00:00
jc21
eaf6335694
Merge pull request #4106 from dreik/develop
All checks were successful
Close stale issues and PRs / stale (push) Successful in 4s
http2 directive migration
2024-10-25 08:53:08 +10:00
jc21
ffe05ebd41
Merge pull request #4108 from chrismaffey/patch-2
Update put.json
2024-10-25 08:06:50 +10:00
Chris Maffey
2e9a4f1aed
Update put.json
Password can be left blank for updates.  Otherwise you have to reenter the password every time you save the auth list
2024-10-24 17:29:16 +13:00
jc21
d17c85e4c8
Merge pull request #4107 from chrismaffey/patch-1
Update _access.conf
2024-10-24 11:31:12 +10:00
Chris Maffey
dad8d0ca00
Update _access.conf
the pass_auth and satisfy_any properties and now boolean true/false, they do not == 1 so the switching in this template breaks
2024-10-24 14:04:17 +13:00
Sergey 'dreik' Kolesnik
d7e0558a35 http2 directive
to reduce warns in logs
2024-10-24 01:30:14 +03:00
jc21
ee41bb5562
Merge pull request #4078 from Guiorgy/patch-1
All checks were successful
Close stale issues and PRs / stale (push) Successful in 4s
normalize indentations in certbot-dns-plugins.json
2024-10-22 10:14:31 +10:00
jc21
0cf6b9caa4
Merge pull request #4084 from ttodua/patch-1
doc(site) - default credentials change
2024-10-22 10:14:11 +10:00
T. Todua
68a9baf206
minor 2024-10-18 15:35:15 +04:00
T. Todua
d92421d098
doc(site) - default credentials change 2024-10-18 15:33:32 +04:00
Guiorgy
96c58b203e
normalize indentations in certbot-dns-plugins.json 2024-10-17 15:34:04 +04:00
Jamie Curnow
d499e2bfef
Push PR and github branch builds to separate docker image
All checks were successful
Close stale issues and PRs / stale (push) Successful in 4s
2024-10-17 10:00:12 +10:00
87 changed files with 2210 additions and 830 deletions

View File

@ -1 +1 @@
2.12.1
2.12.3

52
Jenkinsfile vendored
View File

@ -43,7 +43,7 @@ pipeline {
steps {
script {
// Defaults to the Branch name, which is applies to all branches AND pr's
buildxPushTags = "-t docker.io/jc21/${IMAGE}:github-${BRANCH_LOWER}"
buildxPushTags = "-t docker.io/nginxproxymanager/${IMAGE}-dev:${BRANCH_LOWER}"
}
}
}
@ -128,7 +128,7 @@ pipeline {
sh 'docker-compose down --remove-orphans --volumes -t 30 || true'
}
unstable {
dir(path: 'testing/results') {
dir(path: 'test/results') {
archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml')
}
}
@ -161,7 +161,45 @@ pipeline {
sh 'docker-compose down --remove-orphans --volumes -t 30 || true'
}
unstable {
dir(path: 'testing/results') {
dir(path: 'test/results') {
archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml')
}
}
}
}
stage('Test Postgres') {
environment {
COMPOSE_PROJECT_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}_postgres"
COMPOSE_FILE = 'docker/docker-compose.ci.yml:docker/docker-compose.ci.postgres.yml'
}
when {
not {
equals expected: 'UNSTABLE', actual: currentBuild.result
}
}
steps {
sh 'rm -rf ./test/results/junit/*'
sh './scripts/ci/fulltest-cypress'
}
post {
always {
// Dumps to analyze later
sh 'mkdir -p debug/postgres'
sh 'docker logs $(docker-compose ps --all -q fullstack) > debug/postgres/docker_fullstack.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q stepca) > debug/postgres/docker_stepca.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q pdns) > debug/postgres/docker_pdns.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q pdns-db) > debug/postgres/docker_pdns-db.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q dnsrouter) > debug/postgres/docker_dnsrouter.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q db-postgres) > debug/postgres/docker_db-postgres.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q authentik) > debug/postgres/docker_authentik.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q authentik-redis) > debug/postgres/docker_authentik-redis.log 2>&1'
sh 'docker logs $(docker-compose ps --all -q authentik-ldap) > debug/postgres/docker_authentik-ldap.log 2>&1'
junit 'test/results/junit/*'
sh 'docker-compose down --remove-orphans --volumes -t 30 || true'
}
unstable {
dir(path: 'test/results') {
archiveArtifacts(allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml')
}
}
@ -203,7 +241,13 @@ pipeline {
}
steps {
script {
npmGithubPrComment("Docker Image for build ${BUILD_NUMBER} is available on [DockerHub](https://cloud.docker.com/repository/docker/jc21/${IMAGE}) as `jc21/${IMAGE}:github-${BRANCH_LOWER}`\n\n**Note:** ensure you backup your NPM instance before testing this PR image! Especially if this PR contains database changes.", true)
npmGithubPrComment("""Docker Image for build ${BUILD_NUMBER} is available on
[DockerHub](https://cloud.docker.com/repository/docker/nginxproxymanager/${IMAGE}-dev)
as `nginxproxymanager/${IMAGE}-dev:${BRANCH_LOWER}`
**Note:** ensure you backup your NPM instance before testing this image! Especially if there are database changes
**Note:** this is a different docker image namespace than the official image
""", true)
}
}
}

View File

@ -1,7 +1,7 @@
<p align="center">
<img src="https://nginxproxymanager.com/github.png">
<br><br>
<img src="https://img.shields.io/badge/version-2.12.1-green.svg?style=for-the-badge">
<img src="https://img.shields.io/badge/version-2.12.3-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>

View File

@ -81,7 +81,7 @@ const internalAccessList = {
return internalAccessList.build(row)
.then(() => {
if (row.proxy_host_count) {
if (parseInt(row.proxy_host_count, 10)) {
return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
}
})
@ -223,7 +223,7 @@ const internalAccessList = {
.then((row) => {
return internalAccessList.build(row)
.then(() => {
if (row.proxy_host_count) {
if (parseInt(row.proxy_host_count, 10)) {
return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
}
}).then(internalNginx.reload)
@ -252,9 +252,13 @@ const internalAccessList = {
let query = accessListModel
.query()
.select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count'))
.joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0')
.leftJoin('proxy_host', function() {
this.on('proxy_host.access_list_id', '=', 'access_list.id')
.andOn('proxy_host.is_deleted', '=', 0);
})
.where('access_list.is_deleted', 0)
.andWhere('access_list.id', data.id)
.groupBy('access_list.id')
.allowGraph('[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]')
.first();
@ -373,7 +377,10 @@ const internalAccessList = {
let query = accessListModel
.query()
.select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count'))
.joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0')
.leftJoin('proxy_host', function() {
this.on('proxy_host.access_list_id', '=', 'access_list.id')
.andOn('proxy_host.is_deleted', '=', 0);
})
.where('access_list.is_deleted', 0)
.groupBy('access_list.id')
.allowGraph('[owner,items,clients]')
@ -501,8 +508,13 @@ const internalAccessList = {
if (typeof item.password !== 'undefined' && item.password.length) {
logger.info('Adding: ' + item.username);
utils.execFile('/usr/bin/htpasswd', ['-b', htpasswd_file, item.username, item.password])
.then((/*result*/) => {
utils.execFile('openssl', ['passwd', '-apr1', item.password])
.then((res) => {
try {
fs.appendFileSync(htpasswd_file, item.username + ':' + res + '\n', {encoding: 'utf8'});
} catch (err) {
reject(err);
}
next();
})
.catch((err) => {

View File

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

View File

@ -313,6 +313,9 @@ const internalCertificate = {
.where('is_deleted', 0)
.andWhere('id', data.id)
.allowGraph('[owner]')
.allowGraph('[proxy_hosts]')
.allowGraph('[redirection_hosts]')
.allowGraph('[dead_hosts]')
.first();
if (access_data.permission_visibility !== 'all') {
@ -464,6 +467,9 @@ const internalCertificate = {
.where('is_deleted', 0)
.groupBy('id')
.allowGraph('[owner]')
.allowGraph('[proxy_hosts]')
.allowGraph('[redirection_hosts]')
.allowGraph('[dead_hosts]')
.orderBy('nice_name', 'ASC');
if (access_data.permission_visibility !== 'all') {

View File

@ -6,6 +6,7 @@ const internalHost = require('./host');
const internalNginx = require('./nginx');
const internalAuditLog = require('./audit-log');
const internalCertificate = require('./certificate');
const {castJsonIfNeed} = require('../lib/helpers');
function omissions () {
return ['is_deleted'];
@ -409,16 +410,16 @@ const internalDeadHost = {
.where('is_deleted', 0)
.groupBy('id')
.allowGraph('[owner,certificate]')
.orderBy('domain_names', 'ASC');
.orderBy(castJsonIfNeed('domain_names'), 'ASC');
if (access_data.permission_visibility !== 'all') {
query.andWhere('owner_user_id', access.token.getUserId(1));
}
// Query is used for searching
if (typeof search_query === 'string') {
if (typeof search_query === 'string' && search_query.length > 0) {
query.where(function () {
this.where('domain_names', 'like', '%' + search_query + '%');
this.where(castJsonIfNeed('domain_names'), 'like', '%' + search_query + '%');
});
}

View File

@ -2,6 +2,7 @@ const _ = require('lodash');
const proxyHostModel = require('../models/proxy_host');
const redirectionHostModel = require('../models/redirection_host');
const deadHostModel = require('../models/dead_host');
const {castJsonIfNeed} = require('../lib/helpers');
const internalHost = {
@ -17,7 +18,7 @@ const internalHost = {
cleanSslHstsData: function (data, existing_data) {
existing_data = existing_data === undefined ? {} : existing_data;
let combined_data = _.assign({}, existing_data, data);
const combined_data = _.assign({}, existing_data, data);
if (!combined_data.certificate_id) {
combined_data.ssl_forced = false;
@ -73,7 +74,7 @@ const internalHost = {
* @returns {Promise}
*/
getHostsWithDomains: function (domain_names) {
let promises = [
const promises = [
proxyHostModel
.query()
.where('is_deleted', 0),
@ -125,19 +126,19 @@ const internalHost = {
* @returns {Promise}
*/
isHostnameTaken: function (hostname, ignore_type, ignore_id) {
let promises = [
const promises = [
proxyHostModel
.query()
.where('is_deleted', 0)
.andWhere('domain_names', 'like', '%' + hostname + '%'),
.andWhere(castJsonIfNeed('domain_names'), 'like', '%' + hostname + '%'),
redirectionHostModel
.query()
.where('is_deleted', 0)
.andWhere('domain_names', 'like', '%' + hostname + '%'),
.andWhere(castJsonIfNeed('domain_names'), 'like', '%' + hostname + '%'),
deadHostModel
.query()
.where('is_deleted', 0)
.andWhere('domain_names', 'like', '%' + hostname + '%')
.andWhere(castJsonIfNeed('domain_names'), 'like', '%' + hostname + '%')
];
return Promise.all(promises)

View File

@ -6,6 +6,7 @@ const internalHost = require('./host');
const internalNginx = require('./nginx');
const internalAuditLog = require('./audit-log');
const internalCertificate = require('./certificate');
const {castJsonIfNeed} = require('../lib/helpers');
function omissions () {
return ['is_deleted', 'owner.is_deleted'];
@ -416,16 +417,16 @@ const internalProxyHost = {
.where('is_deleted', 0)
.groupBy('id')
.allowGraph('[owner,access_list,certificate]')
.orderBy('domain_names', 'ASC');
.orderBy(castJsonIfNeed('domain_names'), 'ASC');
if (access_data.permission_visibility !== 'all') {
query.andWhere('owner_user_id', access.token.getUserId(1));
}
// Query is used for searching
if (typeof search_query === 'string') {
if (typeof search_query === 'string' && search_query.length > 0) {
query.where(function () {
this.where('domain_names', 'like', '%' + search_query + '%');
this.where(castJsonIfNeed('domain_names'), 'like', `%${search_query}%`);
});
}

View File

@ -6,6 +6,7 @@ const internalHost = require('./host');
const internalNginx = require('./nginx');
const internalAuditLog = require('./audit-log');
const internalCertificate = require('./certificate');
const {castJsonIfNeed} = require('../lib/helpers');
function omissions () {
return ['is_deleted'];
@ -409,16 +410,16 @@ const internalRedirectionHost = {
.where('is_deleted', 0)
.groupBy('id')
.allowGraph('[owner,certificate]')
.orderBy('domain_names', 'ASC');
.orderBy(castJsonIfNeed('domain_names'), 'ASC');
if (access_data.permission_visibility !== 'all') {
query.andWhere('owner_user_id', access.token.getUserId(1));
}
// Query is used for searching
if (typeof search_query === 'string') {
if (typeof search_query === 'string' && search_query.length > 0) {
query.where(function () {
this.where('domain_names', 'like', '%' + search_query + '%');
this.where(castJsonIfNeed('domain_names'), 'like', `%${search_query}%`);
});
}

View File

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

View File

@ -5,6 +5,8 @@ const authModel = require('../models/auth');
const helpers = require('../lib/helpers');
const TokenModel = require('../models/token');
const ERROR_MESSAGE_INVALID_AUTH = 'Invalid email or password';
module.exports = {
/**
@ -69,15 +71,15 @@ module.exports = {
};
});
} else {
throw new error.AuthError('Invalid password');
throw new error.AuthError(ERROR_MESSAGE_INVALID_AUTH);
}
});
} else {
throw new error.AuthError('No password auth for user');
throw new error.AuthError(ERROR_MESSAGE_INVALID_AUTH);
}
});
} else {
throw new error.AuthError('No relevant user found');
throw new error.AuthError(ERROR_MESSAGE_INVALID_AUTH);
}
});
},

View File

@ -2,7 +2,10 @@ const fs = require('fs');
const NodeRSA = require('node-rsa');
const logger = require('../logger').global;
const keysFile = '/data/keys.json';
const keysFile = '/data/keys.json';
const mysqlEngine = 'mysql2';
const postgresEngine = 'pg';
const sqliteClientName = 'sqlite3';
let instance = null;
@ -14,7 +17,7 @@ const configure = () => {
let configData;
try {
configData = require(filename);
} catch (err) {
} catch (_) {
// do nothing
}
@ -34,7 +37,7 @@ const configure = () => {
logger.info('Using MySQL configuration');
instance = {
database: {
engine: 'mysql2',
engine: mysqlEngine,
host: envMysqlHost,
port: process.env.DB_MYSQL_PORT || 3306,
user: envMysqlUser,
@ -46,13 +49,33 @@ const configure = () => {
return;
}
const envPostgresHost = process.env.DB_POSTGRES_HOST || null;
const envPostgresUser = process.env.DB_POSTGRES_USER || null;
const envPostgresName = process.env.DB_POSTGRES_NAME || null;
if (envPostgresHost && envPostgresUser && envPostgresName) {
// we have enough postgres creds to go with postgres
logger.info('Using Postgres configuration');
instance = {
database: {
engine: postgresEngine,
host: envPostgresHost,
port: process.env.DB_POSTGRES_PORT || 5432,
user: envPostgresUser,
password: process.env.DB_POSTGRES_PASSWORD,
name: envPostgresName,
},
keys: getKeys(),
};
return;
}
const envSqliteFile = process.env.DB_SQLITE_FILE || '/data/database.sqlite';
logger.info(`Using Sqlite: ${envSqliteFile}`);
instance = {
database: {
engine: 'knex-native',
knex: {
client: 'sqlite3',
client: sqliteClientName,
connection: {
filename: envSqliteFile
},
@ -143,7 +166,27 @@ module.exports = {
*/
isSqlite: function () {
instance === null && configure();
return instance.database.knex && instance.database.knex.client === 'sqlite3';
return instance.database.knex && instance.database.knex.client === sqliteClientName;
},
/**
* Is this a mysql configuration?
*
* @returns {boolean}
*/
isMysql: function () {
instance === null && configure();
return instance.database.engine === mysqlEngine;
},
/**
* Is this a postgres configuration?
*
* @returns {boolean}
*/
isPostgres: function () {
instance === null && configure();
return instance.database.engine === postgresEngine;
},
/**

View File

@ -1,4 +1,6 @@
const moment = require('moment');
const moment = require('moment');
const {isPostgres} = require('./config');
const {ref} = require('objection');
module.exports = {
@ -45,6 +47,16 @@ module.exports = {
}
});
return obj;
},
/**
* Casts a column to json if using postgres
*
* @param {string} colName
* @returns {string|Objection.ReferenceBuilder}
*/
castJsonIfNeed: function (colName) {
return isPostgres() ? ref(colName).castText() : colName;
}
};

View File

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

View File

@ -4,7 +4,6 @@
const db = require('../db');
const helpers = require('../lib/helpers');
const Model = require('objection').Model;
const User = require('./user');
const now = require('./now_helper');
Model.knex(db);
@ -68,6 +67,11 @@ class Certificate extends Model {
}
static get relationMappings () {
const ProxyHost = require('./proxy_host');
const DeadHost = require('./dead_host');
const User = require('./user');
const RedirectionHost = require('./redirection_host');
return {
owner: {
relation: Model.HasOneRelation,
@ -79,6 +83,39 @@ class Certificate extends Model {
modify: function (qb) {
qb.where('user.is_deleted', 0);
}
},
proxy_hosts: {
relation: Model.HasManyRelation,
modelClass: ProxyHost,
join: {
from: 'certificate.id',
to: 'proxy_host.certificate_id'
},
modify: function (qb) {
qb.where('proxy_host.is_deleted', 0);
}
},
dead_hosts: {
relation: Model.HasManyRelation,
modelClass: DeadHost,
join: {
from: 'certificate.id',
to: 'dead_host.certificate_id'
},
modify: function (qb) {
qb.where('dead_host.is_deleted', 0);
}
},
redirection_hosts: {
relation: Model.HasManyRelation,
modelClass: RedirectionHost,
join: {
from: 'certificate.id',
to: 'redirection_host.certificate_id'
},
modify: function (qb) {
qb.where('redirection_host.is_deleted', 0);
}
}
};
}

View File

@ -12,7 +12,11 @@ Model.knex(db);
const boolFields = [
'is_deleted',
'ssl_forced',
'http2_support',
'enabled',
'hsts_enabled',
'hsts_subdomains',
];
class DeadHost extends Model {

View File

@ -17,6 +17,9 @@ const boolFields = [
'preserve_path',
'ssl_forced',
'block_exploits',
'hsts_enabled',
'hsts_subdomains',
'http2_support',
];
class RedirectionHost extends Model {

View File

@ -1,16 +1,15 @@
// Objection Docs:
// http://vincit.github.io/objection.js/
const db = require('../db');
const helpers = require('../lib/helpers');
const Model = require('objection').Model;
const User = require('./user');
const now = require('./now_helper');
const Model = require('objection').Model;
const db = require('../db');
const helpers = require('../lib/helpers');
const User = require('./user');
const Certificate = require('./certificate');
const now = require('./now_helper');
Model.knex(db);
const boolFields = [
'is_deleted',
'enabled',
'tcp_forwarding',
'udp_forwarding',
];
@ -64,6 +63,17 @@ class Stream extends Model {
modify: function (qb) {
qb.where('user.is_deleted', 0);
}
},
certificate: {
relation: Model.HasOneRelation,
modelClass: Certificate,
join: {
from: 'stream.certificate_id',
to: 'certificate.id'
},
modify: function (qb) {
qb.where('certificate.is_deleted', 0);
}
}
};
}

View File

@ -23,6 +23,7 @@
"node-rsa": "^1.0.8",
"objection": "3.0.1",
"path": "^0.12.7",
"pg": "^8.13.1",
"signale": "1.4.0",
"sqlite3": "5.1.6",
"temp-write": "^4.0.0"

View File

@ -22,8 +22,7 @@
"enabled",
"locations",
"hsts_enabled",
"hsts_subdomains",
"certificate"
"hsts_subdomains"
],
"additionalProperties": false,
"properties": {

View File

@ -53,8 +53,24 @@
"enabled": {
"$ref": "../common.json#/properties/enabled"
},
"certificate_id": {
"$ref": "../common.json#/properties/certificate_id"
},
"meta": {
"type": "object"
},
"owner": {
"$ref": "./user-object.json"
},
"certificate": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "./certificate-object.json"
}
]
}
}
}

View File

@ -5,10 +5,9 @@
"additionalProperties": false,
"properties": {
"expires": {
"description": "Token Expiry Unix Time",
"example": 1566540249,
"minimum": 1,
"type": "number"
"description": "Token Expiry ISO Time String",
"example": "2025-02-04T20:40:46.340Z",
"type": "string"
},
"token": {
"description": "JWT Token",

View File

@ -49,8 +49,7 @@
"minLength": 1
},
"password": {
"type": "string",
"minLength": 1
"type": "string"
}
}
}

View File

@ -14,7 +14,7 @@
"description": "Expansions",
"schema": {
"type": "string",
"enum": ["access_list", "owner", "certificate"]
"enum": ["owner", "certificate"]
}
}
],
@ -40,7 +40,8 @@
"nginx_online": true,
"nginx_err": null
},
"enabled": true
"enabled": true,
"certificate_id": 0
}
]
}

View File

@ -32,6 +32,9 @@
"udp_forwarding": {
"$ref": "../../../components/stream-object.json#/properties/udp_forwarding"
},
"certificate_id": {
"$ref": "../../../components/stream-object.json#/properties/certificate_id"
},
"meta": {
"$ref": "../../../components/stream-object.json#/properties/meta"
}
@ -73,7 +76,8 @@
"nickname": "Admin",
"avatar": "",
"roles": ["admin"]
}
},
"certificate_id": 0
}
}
},

View File

@ -40,7 +40,8 @@
"nginx_online": true,
"nginx_err": null
},
"enabled": true
"enabled": true,
"certificate_id": 0
}
}
},

View File

@ -29,56 +29,26 @@
"additionalProperties": false,
"minProperties": 1,
"properties": {
"domain_names": {
"$ref": "../../../../components/proxy-host-object.json#/properties/domain_names"
"incoming_port": {
"$ref": "../../../../components/stream-object.json#/properties/incoming_port"
},
"forward_scheme": {
"$ref": "../../../../components/proxy-host-object.json#/properties/forward_scheme"
"forwarding_host": {
"$ref": "../../../../components/stream-object.json#/properties/forwarding_host"
},
"forward_host": {
"$ref": "../../../../components/proxy-host-object.json#/properties/forward_host"
"forwarding_port": {
"$ref": "../../../../components/stream-object.json#/properties/forwarding_port"
},
"forward_port": {
"$ref": "../../../../components/proxy-host-object.json#/properties/forward_port"
"tcp_forwarding": {
"$ref": "../../../../components/stream-object.json#/properties/tcp_forwarding"
},
"udp_forwarding": {
"$ref": "../../../../components/stream-object.json#/properties/udp_forwarding"
},
"certificate_id": {
"$ref": "../../../../components/proxy-host-object.json#/properties/certificate_id"
},
"ssl_forced": {
"$ref": "../../../../components/proxy-host-object.json#/properties/ssl_forced"
},
"hsts_enabled": {
"$ref": "../../../../components/proxy-host-object.json#/properties/hsts_enabled"
},
"hsts_subdomains": {
"$ref": "../../../../components/proxy-host-object.json#/properties/hsts_subdomains"
},
"http2_support": {
"$ref": "../../../../components/proxy-host-object.json#/properties/http2_support"
},
"block_exploits": {
"$ref": "../../../../components/proxy-host-object.json#/properties/block_exploits"
},
"caching_enabled": {
"$ref": "../../../../components/proxy-host-object.json#/properties/caching_enabled"
},
"allow_websocket_upgrade": {
"$ref": "../../../../components/proxy-host-object.json#/properties/allow_websocket_upgrade"
},
"access_list_id": {
"$ref": "../../../../components/proxy-host-object.json#/properties/access_list_id"
},
"advanced_config": {
"$ref": "../../../../components/proxy-host-object.json#/properties/advanced_config"
},
"enabled": {
"$ref": "../../../../components/proxy-host-object.json#/properties/enabled"
"$ref": "../../../../components/stream-object.json#/properties/certificate_id"
},
"meta": {
"$ref": "../../../../components/proxy-host-object.json#/properties/meta"
},
"locations": {
"$ref": "../../../../components/proxy-host-object.json#/properties/locations"
"$ref": "../../../../components/stream-object.json#/properties/meta"
}
}
}
@ -94,42 +64,32 @@
"default": {
"value": {
"id": 1,
"created_on": "2024-10-08T23:23:03.000Z",
"modified_on": "2024-10-08T23:26:37.000Z",
"created_on": "2024-10-09T02:33:45.000Z",
"modified_on": "2024-10-09T02:33:45.000Z",
"owner_user_id": 1,
"domain_names": ["test.example.com"],
"forward_host": "192.168.0.10",
"forward_port": 8989,
"access_list_id": 0,
"certificate_id": 0,
"ssl_forced": false,
"caching_enabled": false,
"block_exploits": false,
"advanced_config": "",
"incoming_port": 9090,
"forwarding_host": "router.internal",
"forwarding_port": 80,
"tcp_forwarding": true,
"udp_forwarding": false,
"meta": {
"nginx_online": true,
"nginx_err": null
},
"allow_websocket_upgrade": false,
"http2_support": false,
"forward_scheme": "http",
"enabled": true,
"hsts_enabled": false,
"hsts_subdomains": false,
"owner": {
"id": 1,
"created_on": "2024-10-07T22:43:55.000Z",
"modified_on": "2024-10-08T12:52:54.000Z",
"created_on": "2024-10-09T02:33:16.000Z",
"modified_on": "2024-10-09T02:33:16.000Z",
"is_deleted": false,
"is_disabled": false,
"email": "admin@example.com",
"name": "Administrator",
"nickname": "some guy",
"avatar": "//www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?default=mm",
"nickname": "Admin",
"avatar": "",
"roles": ["admin"]
},
"certificate": null,
"access_list": null
"certificate_id": 0
}
}
},

View File

@ -15,7 +15,7 @@
"examples": {
"default": {
"value": {
"expires": 1566540510,
"expires": "2025-02-04T20:40:46.340Z",
"token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4"
}
}

View File

@ -38,7 +38,7 @@
"default": {
"value": {
"result": {
"expires": 1566540510,
"expires": "2025-02-04T20:40:46.340Z",
"token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4"
}
}

View File

@ -9,6 +9,15 @@
"url": "http://127.0.0.1:81/api"
}
],
"components": {
"securitySchemes": {
"bearerAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT"
}
}
},
"paths": {
"/": {
"get": {

View File

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

View File

@ -4,7 +4,7 @@
auth_basic "Authorization required";
auth_basic_user_file /data/access/{{ access_list_id }};
{% if access_list.pass_auth == 0 %}
{% if access_list.pass_auth == 0 or access_list.pass_auth == true %}
proxy_set_header Authorization "";
{% endif %}
@ -17,7 +17,7 @@
deny all;
# Access checks must...
{% if access_list.satisfy_any == 1 %}
{% if access_list.satisfy_any == 1 or access_list.satisfy_any == true %}
satisfy any;
{% else %}
satisfy all;

View File

@ -2,6 +2,7 @@
{% if certificate.provider == "letsencrypt" %}
# Let's Encrypt SSL
include conf.d/include/letsencrypt-acme-challenge.conf;
include conf.d/include/ssl-cache.conf;
include conf.d/include/ssl-ciphers.conf;
ssl_certificate /etc/letsencrypt/live/npm-{{ certificate_id }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/npm-{{ certificate_id }}/privkey.pem;

View File

@ -0,0 +1,13 @@
{% if certificate and certificate_id > 0 %}
{% if certificate.provider == "letsencrypt" %}
# Let's Encrypt SSL
include conf.d/include/ssl-cache-stream.conf;
include conf.d/include/ssl-ciphers.conf;
ssl_certificate /etc/letsencrypt/live/npm-{{ certificate_id }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/npm-{{ certificate_id }}/privkey.pem;
{%- else %}
# Custom SSL
ssl_certificate /data/custom_ssl/npm-{{ certificate_id }}/fullchain.pem;
ssl_certificate_key /data/custom_ssl/npm-{{ certificate_id }}/privkey.pem;
{%- endif -%}
{%- endif -%}

View File

@ -5,11 +5,16 @@
#listen [::]:80;
{% endif %}
{% if certificate -%}
listen 443 ssl{% if http2_support == 1 or http2_support == true %} http2{% endif %};
listen 443 ssl;
{% if ipv6 -%}
listen [::]:443 ssl{% if http2_support == 1 or http2_support == true %} http2{% endif %};
listen [::]:443 ssl;
{% else -%}
#listen [::]:443;
{% endif %}
{% endif %}
server_name {{ domain_names | join: " " }};
{% if http2_support == 1 or http2_support == true %}
http2 on;
{% else -%}
http2 off;
{% endif %}

View File

@ -7,11 +7,7 @@
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
set $proxy_forward_scheme {{ forward_scheme }};
set $proxy_server "{{ forward_host }}";
set $proxy_port {{ forward_port }};
proxy_pass $proxy_forward_scheme://$proxy_server:$proxy_port{{ forward_path }};
proxy_pass {{ forward_scheme }}://{{ forward_host }}:{{ forward_port }}{{ forward_path }};
{% include "_access.conf" %}
{% include "_assets.conf" %}

View File

@ -22,5 +22,7 @@ server {
}
{% endif %}
# Custom
include /data/nginx/custom/server_dead[.]conf;
}
{% endif %}

View File

@ -5,12 +5,10 @@
{% if enabled %}
{% if tcp_forwarding == 1 or tcp_forwarding == true -%}
server {
listen {{ incoming_port }};
{% if ipv6 -%}
listen [::]:{{ incoming_port }};
{% else -%}
#listen [::]:{{ incoming_port }};
{% endif %}
listen {{ incoming_port }} {%- if certificate %} ssl {%- endif %};
{% unless ipv6 -%} # {%- endunless -%} listen [::]:{{ incoming_port }} {%- if certificate %} ssl {%- endif %};
{%- include "_certificates_stream.conf" %}
proxy_pass {{ forwarding_host }}:{{ forwarding_port }};
@ -19,14 +17,12 @@ server {
include /data/nginx/custom/server_stream_tcp[.]conf;
}
{% endif %}
{% if udp_forwarding == 1 or udp_forwarding == true %}
{% if udp_forwarding == 1 or udp_forwarding == true -%}
server {
listen {{ incoming_port }} udp;
{% if ipv6 -%}
listen [::]:{{ incoming_port }} udp;
{% else -%}
#listen [::]:{{ incoming_port }} udp;
{% endif %}
{% unless ipv6 -%} # {%- endunless -%} listen [::]:{{ incoming_port }} udp;
proxy_pass {{ forwarding_host }}:{{ forwarding_port }};
# Custom

View File

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

8
docker/ci.env Normal file
View File

@ -0,0 +1,8 @@
AUTHENTIK_SECRET_KEY=gl8woZe8L6IIX8SC0c5Ocsj0xPkX5uJo5DVZCFl+L/QGbzuplfutYuua2ODNLEiDD3aFd9H2ylJmrke0
AUTHENTIK_REDIS__HOST=authentik-redis
AUTHENTIK_POSTGRESQL__HOST=db-postgres
AUTHENTIK_POSTGRESQL__USER=authentik
AUTHENTIK_POSTGRESQL__NAME=authentik
AUTHENTIK_POSTGRESQL__PASSWORD=07EKS5NLI6Tpv68tbdvrxfvj
AUTHENTIK_BOOTSTRAP_PASSWORD=admin
AUTHENTIK_BOOTSTRAP_EMAIL=admin@example.com

Binary file not shown.

View File

@ -29,7 +29,8 @@ COPY scripts/install-s6 /tmp/install-s6
RUN rm -f /etc/nginx/conf.d/production.conf \
&& chmod 644 /etc/logrotate.d/nginx-proxy-manager \
&& /tmp/install-s6 "${TARGETPLATFORM}" \
&& rm -f /tmp/install-s6
&& rm -f /tmp/install-s6 \
&& chmod 644 -R /root/.cache
# Certs for testing purposes
COPY --from=pebbleca /test/certs/pebble.minica.pem /etc/ssl/certs/pebble.minica.pem

View File

@ -18,6 +18,7 @@ services:
MYSQL_DATABASE: 'npm'
MYSQL_USER: 'npm'
MYSQL_PASSWORD: 'npmpass'
MARIADB_AUTO_UPGRADE: '1'
volumes:
- mysql_vol:/var/lib/mysql
networks:

View File

@ -0,0 +1,78 @@
# WARNING: This is a CI docker-compose file used for building and testing of the entire app, it should not be used for production.
services:
cypress:
environment:
CYPRESS_stack: 'postgres'
fullstack:
environment:
DB_POSTGRES_HOST: 'db-postgres'
DB_POSTGRES_PORT: '5432'
DB_POSTGRES_USER: 'npm'
DB_POSTGRES_PASSWORD: 'npmpass'
DB_POSTGRES_NAME: 'npm'
depends_on:
- db-postgres
- authentik
- authentik-worker
- authentik-ldap
db-postgres:
image: postgres:latest
environment:
POSTGRES_USER: 'npm'
POSTGRES_PASSWORD: 'npmpass'
POSTGRES_DB: 'npm'
volumes:
- psql_vol:/var/lib/postgresql/data
- ./ci/postgres:/docker-entrypoint-initdb.d
networks:
- fulltest
authentik-redis:
image: 'redis:alpine'
command: --save 60 1 --loglevel warning
restart: unless-stopped
healthcheck:
test: ['CMD-SHELL', 'redis-cli ping | grep PONG']
start_period: 20s
interval: 30s
retries: 5
timeout: 3s
volumes:
- redis_vol:/data
authentik:
image: ghcr.io/goauthentik/server:2024.10.1
restart: unless-stopped
command: server
env_file:
- ci.env
depends_on:
- authentik-redis
- db-postgres
authentik-worker:
image: ghcr.io/goauthentik/server:2024.10.1
restart: unless-stopped
command: worker
env_file:
- ci.env
depends_on:
- authentik-redis
- db-postgres
authentik-ldap:
image: ghcr.io/goauthentik/ldap:2024.10.1
environment:
AUTHENTIK_HOST: 'http://authentik:9000'
AUTHENTIK_INSECURE: 'true'
AUTHENTIK_TOKEN: 'wKYZuRcI0ETtb8vWzMCr04oNbhrQUUICy89hSpDln1OEKLjiNEuQ51044Vkp'
restart: unless-stopped
depends_on:
- authentik
volumes:
psql_vol:
redis_vol:

View File

@ -22,6 +22,10 @@ services:
test: ["CMD", "/usr/bin/check-health"]
interval: 10s
timeout: 3s
expose:
- '80-81/tcp'
- '443/tcp'
- '1500-1503/tcp'
networks:
fulltest:
aliases:
@ -40,7 +44,7 @@ services:
- ca.internal
pdns:
image: pschiffe/pdns-mysql
image: pschiffe/pdns-mysql:4.8
volumes:
- '/etc/localtime:/etc/localtime:ro'
environment:
@ -97,7 +101,7 @@ services:
HTTP_PROXY: 'squid:3128'
HTTPS_PROXY: 'squid:3128'
volumes:
- 'cypress_logs:/results'
- 'cypress_logs:/test/results'
- './dev/resolv.conf:/etc/resolv.conf:ro'
- '/etc/localtime:/etc/localtime:ro'
command: cypress run --browser chrome --config-file=cypress/config/ci.js

View File

@ -2,8 +2,8 @@
services:
fullstack:
image: nginxproxymanager:dev
container_name: npm_core
image: npm2dev:core
container_name: npm2dev.core
build:
context: ./
dockerfile: ./dev/Dockerfile
@ -26,11 +26,17 @@ services:
DEVELOPMENT: 'true'
LE_STAGING: 'true'
# db:
DB_MYSQL_HOST: 'db'
DB_MYSQL_PORT: '3306'
DB_MYSQL_USER: 'npm'
DB_MYSQL_PASSWORD: 'npm'
DB_MYSQL_NAME: 'npm'
# DB_MYSQL_HOST: 'db'
# DB_MYSQL_PORT: '3306'
# DB_MYSQL_USER: 'npm'
# DB_MYSQL_PASSWORD: 'npm'
# DB_MYSQL_NAME: 'npm'
# db-postgres:
DB_POSTGRES_HOST: 'db-postgres'
DB_POSTGRES_PORT: '5432'
DB_POSTGRES_USER: 'npm'
DB_POSTGRES_PASSWORD: 'npmpass'
DB_POSTGRES_NAME: 'npm'
# DB_SQLITE_FILE: "/data/database.sqlite"
# DISABLE_IPV6: "true"
# Required for DNS Certificate provisioning testing:
@ -49,11 +55,15 @@ services:
timeout: 3s
depends_on:
- db
- db-postgres
- authentik
- authentik-worker
- authentik-ldap
working_dir: /app
db:
image: jc21/mariadb-aria
container_name: npm_db
container_name: npm2dev.db
ports:
- 33306:3306
networks:
@ -66,8 +76,22 @@ services:
volumes:
- db_data:/var/lib/mysql
db-postgres:
image: postgres:latest
container_name: npm2dev.db-postgres
networks:
- nginx_proxy_manager
environment:
POSTGRES_USER: 'npm'
POSTGRES_PASSWORD: 'npmpass'
POSTGRES_DB: 'npm'
volumes:
- psql_data:/var/lib/postgresql/data
- ./ci/postgres:/docker-entrypoint-initdb.d
stepca:
image: jc21/testca
container_name: npm2dev.stepca
volumes:
- './dev/resolv.conf:/etc/resolv.conf:ro'
- '/etc/localtime:/etc/localtime:ro'
@ -78,6 +102,7 @@ services:
dnsrouter:
image: jc21/dnsrouter
container_name: npm2dev.dnsrouter
volumes:
- ./dev/dnsrouter-config.json.tmp:/dnsrouter-config.json:ro
networks:
@ -85,7 +110,7 @@ services:
swagger:
image: swaggerapi/swagger-ui:latest
container_name: npm_swagger
container_name: npm2dev.swagger
ports:
- 3082:80
environment:
@ -96,7 +121,7 @@ services:
squid:
image: ubuntu/squid
container_name: npm_squid
container_name: npm2dev.squid
volumes:
- './dev/squid.conf:/etc/squid/squid.conf:ro'
- './dev/resolv.conf:/etc/resolv.conf:ro'
@ -107,7 +132,8 @@ services:
- 8128:3128
pdns:
image: pschiffe/pdns-mysql
image: pschiffe/pdns-mysql:4.8
container_name: npm2dev.pdns
volumes:
- '/etc/localtime:/etc/localtime:ro'
environment:
@ -136,6 +162,7 @@ services:
pdns-db:
image: mariadb
container_name: npm2dev.pdns-db
environment:
MYSQL_ROOT_PASSWORD: 'pdns'
MYSQL_DATABASE: 'pdns'
@ -149,7 +176,8 @@ services:
- nginx_proxy_manager
cypress:
image: "npm_dev_cypress"
image: npm2dev:cypress
container_name: npm2dev.cypress
build:
context: ../
dockerfile: test/cypress/Dockerfile
@ -164,16 +192,77 @@ services:
networks:
- nginx_proxy_manager
authentik-redis:
image: 'redis:alpine'
container_name: npm2dev.authentik-redis
command: --save 60 1 --loglevel warning
networks:
- nginx_proxy_manager
restart: unless-stopped
healthcheck:
test: ['CMD-SHELL', 'redis-cli ping | grep PONG']
start_period: 20s
interval: 30s
retries: 5
timeout: 3s
volumes:
- redis_data:/data
authentik:
image: ghcr.io/goauthentik/server:2024.10.1
container_name: npm2dev.authentik
restart: unless-stopped
command: server
networks:
- nginx_proxy_manager
env_file:
- ci.env
ports:
- 9000:9000
depends_on:
- authentik-redis
- db-postgres
authentik-worker:
image: ghcr.io/goauthentik/server:2024.10.1
container_name: npm2dev.authentik-worker
restart: unless-stopped
command: worker
networks:
- nginx_proxy_manager
env_file:
- ci.env
depends_on:
- authentik-redis
- db-postgres
authentik-ldap:
image: ghcr.io/goauthentik/ldap:2024.10.1
container_name: npm2dev.authentik-ldap
networks:
- nginx_proxy_manager
environment:
AUTHENTIK_HOST: 'http://authentik:9000'
AUTHENTIK_INSECURE: 'true'
AUTHENTIK_TOKEN: 'wKYZuRcI0ETtb8vWzMCr04oNbhrQUUICy89hSpDln1OEKLjiNEuQ51044Vkp'
restart: unless-stopped
depends_on:
- authentik
volumes:
npm_data:
name: npm_core_data
name: npm2dev_core_data
le_data:
name: npm_le_data
name: npm2dev_le_data
db_data:
name: npm_db_data
name: npm2dev_db_data
pdns_mysql:
name: npm_pdns_mysql
name: npnpm2dev_pdns_mysql
psql_data:
name: npm2dev_psql_data
redis_data:
name: npm2dev_redis_data
networks:
nginx_proxy_manager:
name: npm_network
name: npm2dev_network

View File

@ -1,4 +1,4 @@
location ~* ^.*\.(css|js|jpe?g|gif|png|webp|woff|eot|ttf|svg|ico|css\.map|js\.map)$ {
location ~* ^.*\.(css|js|jpe?g|gif|png|webp|woff|woff2|eot|ttf|svg|ico|css\.map|js\.map)$ {
if_modified_since off;
# use the public cache

View File

@ -0,0 +1,2 @@
ssl_session_timeout 5m;
ssl_session_cache shared:SSL_stream:50m;

View File

@ -0,0 +1,2 @@
ssl_session_timeout 5m;
ssl_session_cache shared:SSL:50m;

View File

@ -1,6 +1,3 @@
ssl_session_timeout 5m;
ssl_session_cache shared:SSL:50m;
# intermediate configuration. tweak to your needs.
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';

View File

@ -8,7 +8,7 @@ BLUE='\E[1;34m'
GREEN='\E[1;32m'
RESET='\E[0m'
S6_OVERLAY_VERSION=3.1.5.0
S6_OVERLAY_VERSION=3.2.0.2
TARGETPLATFORM=${1:-linux/amd64}
# Determine the correct binary file for the architecture given

View File

@ -50,7 +50,6 @@ networks:
Let's look at a Portainer example:
```yml
version: '3.8'
services:
portainer:
@ -92,8 +91,6 @@ This image supports the use of Docker secrets to import from files and keep sens
You can set any environment variable from a file by appending `__FILE` (double-underscore FILE) to the environmental variable name.
```yml
version: '3.8'
secrets:
# Secrets are single-line text files where the sole content is the secret
# Paths in this example assume that secrets are kept in local folder called ".secrets"
@ -184,6 +181,7 @@ You can add your custom configuration snippet files at `/data/nginx/custom` as f
- `/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/server_dead.conf`: Included at the end of every 404 server block
Every file is optional.

View File

@ -9,7 +9,6 @@ outline: deep
Create a `docker-compose.yml` file:
```yml
version: '3.8'
services:
app:
image: 'jc21/nginx-proxy-manager:latest'
@ -22,8 +21,7 @@ services:
# Add any other Stream port you want to expose
# - '21:21' # FTP
# Uncomment the next line if you uncomment anything in the section
# environment:
environment:
# Uncomment this if you want to change the location of
# the SQLite DB file within the container
# DB_SQLITE_FILE: "/data/database.sqlite"
@ -55,7 +53,6 @@ are going to use.
Here is an example of what your `docker-compose.yml` will look like when using a MariaDB container:
```yml
version: '3.8'
services:
app:
image: 'jc21/nginx-proxy-manager:latest'
@ -101,6 +98,53 @@ Please note, that `DB_MYSQL_*` environment variables will take precedent over `D
:::
## Using Postgres database
Similar to the MySQL server setup:
```yml
services:
app:
image: 'jc21/nginx-proxy-manager:latest'
restart: unless-stopped
ports:
# These ports are in format <host-port>:<container-port>
- '80:80' # Public HTTP Port
- '443:443' # Public HTTPS Port
- '81:81' # Admin Web Port
# Add any other Stream port you want to expose
# - '21:21' # FTP
environment:
# Postgres parameters:
DB_POSTGRES_HOST: 'db'
DB_POSTGRES_PORT: '5432'
DB_POSTGRES_USER: 'npm'
DB_POSTGRES_PASSWORD: 'npmpass'
DB_POSTGRES_NAME: 'npm'
# Uncomment this if IPv6 is not enabled on your host
# DISABLE_IPV6: 'true'
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
depends_on:
- db
db:
image: postgres:latest
environment:
POSTGRES_USER: 'npm'
POSTGRES_PASSWORD: 'npmpass'
POSTGRES_DB: 'npm'
volumes:
- ./postgres:/var/lib/postgresql/data
```
::: warning
Custom Postgres schema is not supported, as such `public` will be used.
:::
## Running on Raspberry PI / ARM devices
The docker images support the following architectures:
@ -137,5 +181,13 @@ Email: admin@example.com
Password: changeme
```
Immediately after logging in with this default user you will be asked to modify your details and change your password.
Immediately after logging in with this default user you will be asked to modify your details and change your password. You can change defaults with:
```
environment:
INITIAL_ADMIN_EMAIL: my@example.com
INITIAL_ADMIN_PASSWORD: mypassword1
```

View File

@ -12,6 +12,7 @@ Known integrations:
- [HomeAssistant Hass.io plugin](https://github.com/hassio-addons/addon-nginx-proxy-manager)
- [UnRaid / Synology](https://github.com/jlesage/docker-nginx-proxy-manager)
- [Proxmox Scripts](https://github.com/ej52/proxmox-scripts/tree/main/apps/nginx-proxy-manager)
- [Proxmox VE Helper-Scripts](https://community-scripts.github.io/ProxmoxVE/scripts?id=nginxproxymanager)
- [nginxproxymanagerGraf](https://github.com/ma-karai/nginxproxymanagerGraf)

View File

@ -873,9 +873,9 @@ mitt@^3.0.1:
integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==
nanoid@^3.3.7:
version "3.3.7"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
version "3.3.8"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf"
integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==
oniguruma-to-js@0.4.3:
version "0.4.3"
@ -1065,9 +1065,9 @@ vfile@^6.0.0:
vfile-message "^4.0.0"
vite@^5.4.8:
version "5.4.8"
resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.8.tgz#af548ce1c211b2785478d3ba3e8da51e39a287e8"
integrity sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==
version "5.4.14"
resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.14.tgz#ff8255edb02134df180dcfca1916c37a6abe8408"
integrity sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==
dependencies:
esbuild "^0.21.3"
postcss "^8.4.43"

View File

@ -4,444 +4,438 @@ const Tokens = require('./tokens');
module.exports = {
/**
* @param {String} route
* @param {Object} [options]
* @returns {Boolean}
*/
navigate: function (route, options) {
options = options || {};
Backbone.history.navigate(route.toString(), options);
return true;
},
/**
* @param {String} route
* @param {Object} [options]
* @returns {Boolean}
*/
navigate: function (route, options) {
options = options || {};
Backbone.history.navigate(route.toString(), options);
return true;
},
/**
* Login
*/
showLogin: function () {
window.location = '/login';
},
/**
* Login
*/
showLogin: function () {
window.location = '/login';
},
/**
* Users
*/
showUsers: function () {
let controller = this;
if (Cache.User.isAdmin()) {
require(['./main', './users/main'], (App, View) => {
controller.navigate('/users');
App.UI.showAppContent(new View());
});
} else {
this.showDashboard();
}
},
/**
* Users
*/
showUsers: function () {
const controller = this;
if (Cache.User.isAdmin()) {
require(['./main', './users/main'], (App, View) => {
controller.navigate('/users');
App.UI.showAppContent(new View());
});
} else {
this.showDashboard();
}
},
/**
* User Form
*
* @param [model]
*/
showUserForm: function (model) {
if (Cache.User.isAdmin()) {
require(['./main', './user/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* User Form
*
* @param [model]
*/
showUserForm: function (model) {
if (Cache.User.isAdmin()) {
require(['./main', './user/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* User Permissions Form
*
* @param model
*/
showUserPermissions: function (model) {
if (Cache.User.isAdmin()) {
require(['./main', './user/permissions'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* User Permissions Form
*
* @param model
*/
showUserPermissions: function (model) {
if (Cache.User.isAdmin()) {
require(['./main', './user/permissions'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* User Password Form
*
* @param model
*/
showUserPasswordForm: function (model) {
if (Cache.User.isAdmin() || model.get('id') === Cache.User.get('id')) {
require(['./main', './user/password'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* User Password Form
*
* @param model
*/
showUserPasswordForm: function (model) {
if (Cache.User.isAdmin() || model.get('id') === Cache.User.get('id')) {
require(['./main', './user/password'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* User Delete Confirm
*
* @param model
*/
showUserDeleteConfirm: function (model) {
if (Cache.User.isAdmin() && model.get('id') !== Cache.User.get('id')) {
require(['./main', './user/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* User Delete Confirm
*
* @param model
*/
showUserDeleteConfirm: function (model) {
if (Cache.User.isAdmin() && model.get('id') !== Cache.User.get('id')) {
require(['./main', './user/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Dashboard
*/
showDashboard: function () {
let controller = this;
/**
* Dashboard
*/
showDashboard: function () {
const controller = this;
require(['./main', './dashboard/main'], (App, View) => {
controller.navigate('/');
App.UI.showAppContent(new View());
});
},
require(['./main', './dashboard/main'], (App, View) => {
controller.navigate('/');
App.UI.showAppContent(new View());
});
},
/**
* Nginx Proxy Hosts
*/
showNginxProxy: function () {
if (Cache.User.isAdmin() || Cache.User.canView('proxy_hosts')) {
const controller = this;
/**
* Nginx Proxy Hosts
*/
showNginxProxy: function () {
if (Cache.User.isAdmin() || Cache.User.canView('proxy_hosts')) {
let controller = this;
require(['./main', './nginx/proxy/main'], (App, View) => {
controller.navigate('/nginx/proxy');
App.UI.showAppContent(new View());
});
}
},
require(['./main', './nginx/proxy/main'], (App, View) => {
controller.navigate('/nginx/proxy');
App.UI.showAppContent(new View());
});
}
},
/**
* Nginx Proxy Host Form
*
* @param [model]
*/
showNginxProxyForm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) {
require(['./main', './nginx/proxy/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Nginx Proxy Host Form
*
* @param [model]
*/
showNginxProxyForm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) {
require(['./main', './nginx/proxy/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Proxy Host Delete Confirm
*
* @param model
*/
showNginxProxyDeleteConfirm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) {
require(['./main', './nginx/proxy/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Proxy Host Delete Confirm
*
* @param model
*/
showNginxProxyDeleteConfirm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) {
require(['./main', './nginx/proxy/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Nginx Redirection Hosts
*/
showNginxRedirection: function () {
if (Cache.User.isAdmin() || Cache.User.canView('redirection_hosts')) {
const controller = this;
require(['./main', './nginx/redirection/main'], (App, View) => {
controller.navigate('/nginx/redirection');
App.UI.showAppContent(new View());
});
}
},
/**
* Nginx Redirection Hosts
*/
showNginxRedirection: function () {
if (Cache.User.isAdmin() || Cache.User.canView('redirection_hosts')) {
let controller = this;
/**
* Nginx Redirection Host Form
*
* @param [model]
*/
showNginxRedirectionForm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) {
require(['./main', './nginx/redirection/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
require(['./main', './nginx/redirection/main'], (App, View) => {
controller.navigate('/nginx/redirection');
App.UI.showAppContent(new View());
});
}
},
/**
* Proxy Redirection Delete Confirm
*
* @param model
*/
showNginxRedirectionDeleteConfirm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) {
require(['./main', './nginx/redirection/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Nginx Redirection Host Form
*
* @param [model]
*/
showNginxRedirectionForm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) {
require(['./main', './nginx/redirection/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Nginx Stream Hosts
*/
showNginxStream: function () {
if (Cache.User.isAdmin() || Cache.User.canView('streams')) {
const controller = this;
require(['./main', './nginx/stream/main'], (App, View) => {
controller.navigate('/nginx/stream');
App.UI.showAppContent(new View());
});
}
},
/**
* Proxy Redirection Delete Confirm
*
* @param model
*/
showNginxRedirectionDeleteConfirm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) {
require(['./main', './nginx/redirection/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Stream Form
*
* @param [model]
*/
showNginxStreamForm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('streams')) {
require(['./main', './nginx/stream/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Nginx Stream Hosts
*/
showNginxStream: function () {
if (Cache.User.isAdmin() || Cache.User.canView('streams')) {
let controller = this;
/**
* Stream Delete Confirm
*
* @param model
*/
showNginxStreamDeleteConfirm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('streams')) {
require(['./main', './nginx/stream/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
require(['./main', './nginx/stream/main'], (App, View) => {
controller.navigate('/nginx/stream');
App.UI.showAppContent(new View());
});
}
},
/**
* Nginx Dead Hosts
*/
showNginxDead: function () {
if (Cache.User.isAdmin() || Cache.User.canView('dead_hosts')) {
const controller = this;
require(['./main', './nginx/dead/main'], (App, View) => {
controller.navigate('/nginx/404');
App.UI.showAppContent(new View());
});
}
},
/**
* Stream Form
*
* @param [model]
*/
showNginxStreamForm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('streams')) {
require(['./main', './nginx/stream/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Dead Host Form
*
* @param [model]
*/
showNginxDeadForm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) {
require(['./main', './nginx/dead/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Stream Delete Confirm
*
* @param model
*/
showNginxStreamDeleteConfirm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('streams')) {
require(['./main', './nginx/stream/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Dead Host Delete Confirm
*
* @param model
*/
showNginxDeadDeleteConfirm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) {
require(['./main', './nginx/dead/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Nginx Dead Hosts
*/
showNginxDead: function () {
if (Cache.User.isAdmin() || Cache.User.canView('dead_hosts')) {
let controller = this;
/**
* Help Dialog
*
* @param {String} title
* @param {String} content
*/
showHelp: function (title, content) {
require(['./main', './help/main'], function (App, View) {
App.UI.showModalDialog(new View({title: title, content: content}));
});
},
require(['./main', './nginx/dead/main'], (App, View) => {
controller.navigate('/nginx/404');
App.UI.showAppContent(new View());
});
}
},
/**
* Nginx Access
*/
showNginxAccess: function () {
if (Cache.User.isAdmin() || Cache.User.canView('access_lists')) {
const controller = this;
require(['./main', './nginx/access/main'], (App, View) => {
controller.navigate('/nginx/access');
App.UI.showAppContent(new View());
});
}
},
/**
* Dead Host Form
*
* @param [model]
*/
showNginxDeadForm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) {
require(['./main', './nginx/dead/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Nginx Access List Form
*
* @param [model]
*/
showNginxAccessListForm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) {
require(['./main', './nginx/access/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Dead Host Delete Confirm
*
* @param model
*/
showNginxDeadDeleteConfirm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) {
require(['./main', './nginx/dead/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Access List Delete Confirm
*
* @param model
*/
showNginxAccessListDeleteConfirm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) {
require(['./main', './nginx/access/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Help Dialog
*
* @param {String} title
* @param {String} content
*/
showHelp: function (title, content) {
require(['./main', './help/main'], function (App, View) {
App.UI.showModalDialog(new View({title: title, content: content}));
});
},
/**
* Nginx Certificates
*/
showNginxCertificates: function () {
if (Cache.User.isAdmin() || Cache.User.canView('certificates')) {
const controller = this;
require(['./main', './nginx/certificates/main'], (App, View) => {
controller.navigate('/nginx/certificates');
App.UI.showAppContent(new View());
});
}
},
/**
* Nginx Access
*/
showNginxAccess: function () {
if (Cache.User.isAdmin() || Cache.User.canView('access_lists')) {
let controller = this;
/**
* Nginx Certificate Form
*
* @param [model]
*/
showNginxCertificateForm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) {
require(['./main', './nginx/certificates/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
require(['./main', './nginx/access/main'], (App, View) => {
controller.navigate('/nginx/access');
App.UI.showAppContent(new View());
});
}
},
/**
* Certificate Renew
*
* @param model
*/
showNginxCertificateRenew: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) {
require(['./main', './nginx/certificates/renew'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Nginx Access List Form
*
* @param [model]
*/
showNginxAccessListForm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) {
require(['./main', './nginx/access/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Certificate Delete Confirm
*
* @param model
*/
showNginxCertificateDeleteConfirm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) {
require(['./main', './nginx/certificates/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Access List Delete Confirm
*
* @param model
*/
showNginxAccessListDeleteConfirm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) {
require(['./main', './nginx/access/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Certificate Test Reachability
*
* @param model
*/
showNginxCertificateTestReachability: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) {
require(['./main', './nginx/certificates/test'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Nginx Certificates
*/
showNginxCertificates: function () {
if (Cache.User.isAdmin() || Cache.User.canView('certificates')) {
let controller = this;
/**
* Audit Log
*/
showAuditLog: function () {
const controller = this;
if (Cache.User.isAdmin()) {
require(['./main', './audit-log/main'], (App, View) => {
controller.navigate('/audit-log');
App.UI.showAppContent(new View());
});
} else {
this.showDashboard();
}
},
require(['./main', './nginx/certificates/main'], (App, View) => {
controller.navigate('/nginx/certificates');
App.UI.showAppContent(new View());
});
}
},
/**
* Audit Log Metadata
*
* @param model
*/
showAuditMeta: function (model) {
if (Cache.User.isAdmin()) {
require(['./main', './audit-log/meta'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Nginx Certificate Form
*
* @param [model]
*/
showNginxCertificateForm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) {
require(['./main', './nginx/certificates/form'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Settings
*/
showSettings: function () {
const controller = this;
if (Cache.User.isAdmin()) {
require(['./main', './settings/main'], (App, View) => {
controller.navigate('/settings');
App.UI.showAppContent(new View());
});
} else {
this.showDashboard();
}
},
/**
* Certificate Renew
*
* @param model
*/
showNginxCertificateRenew: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) {
require(['./main', './nginx/certificates/renew'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Settings Item Form
*
* @param model
*/
showSettingForm: function (model) {
if (Cache.User.isAdmin()) {
if (model.get('id') === 'default-site') {
require(['./main', './settings/default-site/main'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
}
},
/**
* Certificate Delete Confirm
*
* @param model
*/
showNginxCertificateDeleteConfirm: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) {
require(['./main', './nginx/certificates/delete'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Certificate Test Reachability
*
* @param model
*/
showNginxCertificateTestReachability: function (model) {
if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) {
require(['./main', './nginx/certificates/test'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Audit Log
*/
showAuditLog: function () {
let controller = this;
if (Cache.User.isAdmin()) {
require(['./main', './audit-log/main'], (App, View) => {
controller.navigate('/audit-log');
App.UI.showAppContent(new View());
});
} else {
this.showDashboard();
}
},
/**
* Audit Log Metadata
*
* @param model
*/
showAuditMeta: function (model) {
if (Cache.User.isAdmin()) {
require(['./main', './audit-log/meta'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
},
/**
* Settings
*/
showSettings: function () {
let controller = this;
if (Cache.User.isAdmin()) {
require(['./main', './settings/main'], (App, View) => {
controller.navigate('/settings');
App.UI.showAppContent(new View());
});
} else {
this.showDashboard();
}
},
/**
* Settings Item Form
*
* @param model
*/
showSettingForm: function (model) {
if (Cache.User.isAdmin()) {
if (model.get('id') === 'default-site') {
require(['./main', './settings/default-site/main'], function (App, View) {
App.UI.showModalDialog(new View({model: model}));
});
}
}
},
/**
* Logout
*/
logout: function () {
Tokens.dropTopToken();
this.showLogin();
}
/**
* Logout
*/
logout: function () {
Tokens.dropTopToken();
this.showLogin();
}
};

View File

@ -6,87 +6,85 @@ const Helpers = require('../../lib/helpers');
const template = require('./main.ejs');
module.exports = Mn.View.extend({
template: template,
id: 'dashboard',
columns: 0,
template: template,
id: 'dashboard',
columns: 0,
stats: {},
stats: {},
ui: {
links: 'a'
},
ui: {
links: 'a'
},
events: {
'click @ui.links': function (e) {
e.preventDefault();
Controller.navigate($(e.currentTarget).attr('href'), true);
}
},
events: {
'click @ui.links': function (e) {
e.preventDefault();
Controller.navigate($(e.currentTarget).attr('href'), true);
}
},
templateContext: function () {
let view = this;
templateContext: function () {
const view = this;
return {
getUserName: function () {
return Cache.User.get('nickname') || Cache.User.get('name');
},
return {
getUserName: function () {
return Cache.User.get('nickname') || Cache.User.get('name');
},
getHostStat: function (type) {
if (view.stats && typeof view.stats.hosts !== 'undefined' && typeof view.stats.hosts[type] !== 'undefined') {
return Helpers.niceNumber(view.stats.hosts[type]);
}
getHostStat: function (type) {
if (view.stats && typeof view.stats.hosts !== 'undefined' && typeof view.stats.hosts[type] !== 'undefined') {
return Helpers.niceNumber(view.stats.hosts[type]);
}
return '-';
},
return '-';
},
canShow: function (perm) {
return Cache.User.isAdmin() || Cache.User.canView(perm);
},
canShow: function (perm) {
return Cache.User.isAdmin() || Cache.User.canView(perm);
},
columns: view.columns
};
},
columns: view.columns
};
},
onRender: function () {
let view = this;
onRender: function () {
const view = this;
if (typeof view.stats.hosts === 'undefined') {
Api.Reports.getHostStats()
.then(response => {
if (!view.isDestroyed()) {
view.stats.hosts = response;
view.render();
}
})
.catch(err => {
console.log(err);
});
}
},
if (typeof view.stats.hosts === 'undefined') {
Api.Reports.getHostStats()
.then(response => {
if (!view.isDestroyed()) {
view.stats.hosts = response;
view.render();
}
})
.catch(err => {
console.log(err);
});
}
},
/**
* @param {Object} [model]
*/
preRender: function (model) {
this.columns = 0;
/**
* @param {Object} [model]
*/
preRender: function (model) {
this.columns = 0;
// calculate the available columns based on permissions for the objects
// and store as a variable
const perms = ['proxy_hosts', 'redirection_hosts', 'streams', 'dead_hosts'];
// calculate the available columns based on permissions for the objects
// and store as a variable
//let view = this;
let perms = ['proxy_hosts', 'redirection_hosts', 'streams', 'dead_hosts'];
perms.map(perm => {
this.columns += Cache.User.isAdmin() || Cache.User.canView(perm) ? 1 : 0;
});
perms.map(perm => {
this.columns += Cache.User.isAdmin() || Cache.User.canView(perm) ? 1 : 0;
});
// Prevent double rendering on initial calls
if (typeof model !== 'undefined') {
this.render();
}
},
// Prevent double rendering on initial calls
if (typeof model !== 'undefined') {
this.render();
}
},
initialize: function () {
this.preRender();
this.listenTo(Cache.User, 'change', this.preRender);
}
initialize: function () {
this.preRender();
this.listenTo(Cache.User, 'change', this.preRender);
}
});

View File

@ -1,6 +1,6 @@
<td class="text-center">
<div class="avatar d-block" style="background-image: url(<%- owner.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>">
<span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span>
<div class="avatar d-block" style="background-image: url(<%- (owner && owner.avatar) || '/images/default-avatar.jpg' %>)" title="Owned by <%- (owner && owner.name) || 'a deleted user' %>">
<span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span>
</div>
</td>
<td>

View File

@ -1,6 +1,6 @@
<td class="text-center">
<div class="avatar d-block" style="background-image: url(<%- owner.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>">
<span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span>
<div class="avatar d-block" style="background-image: url(<%- (owner && owner.avatar) || '/images/default-avatar.jpg' %>)" title="Owned by <%- (owner && owner.name) || 'a deleted user' %>">
<span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span>
</div>
</td>
<td>
@ -33,6 +33,13 @@
<td class="<%- isExpired() ? 'text-danger' : '' %>">
<%- formatDbDate(expires_on, 'Do MMMM YYYY, h:mm a') %>
</td>
<td>
<% if (active_domain_names().length > 0) { %>
<span class="status-icon bg-success"></span> <%- i18n('certificates', 'in-use') %>
<% } else { %>
<span class="status-icon bg-danger"></span> <%- i18n('certificates', 'inactive') %>
<% } %>
</td>
<% if (canManage) { %>
<td class="text-right">
<div class="item-action dropdown">
@ -48,7 +55,14 @@
<div class="dropdown-divider"></div>
<% } %>
<a href="#" class="delete dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> <%- i18n('str', 'delete') %></a>
<% if (active_domain_names().length > 0) { %>
<div class="dropdown-divider"></div>
<span class="dropdown-header"><%- i18n('certificates', 'active-domain_names') %></span>
<% active_domain_names().forEach(function(host) { %>
<a href="https://<%- host %>" class="dropdown-item" target="_blank"><%- host %></a>
<% }); %>
<% } %>
</div>
</div>
</td>
<% } %>
<% } %>

View File

@ -44,14 +44,24 @@ module.exports = Mn.View.extend({
},
},
templateContext: {
canManage: App.Cache.User.canManage('certificates'),
isExpired: function () {
return moment(this.expires_on).isBefore(moment());
},
dns_providers: dns_providers
templateContext: function () {
return {
canManage: App.Cache.User.canManage('certificates'),
isExpired: function () {
return moment(this.expires_on).isBefore(moment());
},
dns_providers: dns_providers,
active_domain_names: function () {
const { proxy_hosts = [], redirect_hosts = [], dead_hosts = [] } = this;
return [...proxy_hosts, ...redirect_hosts, ...dead_hosts].reduce((acc, host) => {
acc.push(...(host.domain_names || []));
return acc;
}, []);
}
};
},
initialize: function () {
this.listenTo(this.model, 'change', this.render);
}

View File

@ -3,6 +3,7 @@
<th><%- i18n('str', 'name') %></th>
<th><%- i18n('all-hosts', 'cert-provider') %></th>
<th><%- i18n('str', 'expires') %></th>
<th><%- i18n('str', 'status') %></th>
<% if (canManage) { %>
<th>&nbsp;</th>
<% } %>

View File

@ -74,7 +74,7 @@ module.exports = Mn.View.extend({
e.preventDefault();
let query = this.ui.query.val();
this.fetch(['owner'], query)
this.fetch(['owner','proxy_hosts', 'dead_hosts', 'redirection_hosts'], query)
.then(response => this.showData(response))
.catch(err => {
this.showError(err);
@ -89,7 +89,7 @@ module.exports = Mn.View.extend({
onRender: function () {
let view = this;
view.fetch(['owner'])
view.fetch(['owner','proxy_hosts', 'dead_hosts', 'redirection_hosts'])
.then(response => {
if (!view.isDestroyed()) {
if (response && response.length) {

View File

@ -1,6 +1,6 @@
<td class="text-center">
<div class="avatar d-block" style="background-image: url(<%- owner.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>">
<span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span>
<div class="avatar d-block" style="background-image: url(<%- (owner && owner.avatar) || '/images/default-avatar.jpg' %>)" title="Owned by <%- (owner && owner.name) || 'a deleted user' %>">
<span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span>
</div>
</td>
<td>

View File

@ -1,6 +1,6 @@
<td class="text-center">
<div class="avatar d-block" style="background-image: url(<%- owner.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>">
<span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span>
<div class="avatar d-block" style="background-image: url(<%- (owner && owner.avatar) || '/images/default-avatar.jpg' %>)" title="Owned by <%- (owner && owner.name) || 'a deleted user' %>">
<span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span>
</div>
</td>
<td>

View File

@ -1,6 +1,6 @@
<td class="text-center">
<div class="avatar d-block" style="background-image: url(<%- owner.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>">
<span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span>
<div class="avatar d-block" style="background-image: url(<%- (owner && owner.avatar) || '/images/default-avatar.jpg' %>)" title="Owned by <%- (owner && owner.name) || 'a deleted user' %>">
<span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span>
</div>
</td>
<td>

View File

@ -3,48 +3,187 @@
<h5 class="modal-title"><%- i18n('streams', 'form-title', {id: id}) %></h5>
<button type="button" class="close cancel" aria-label="Close" data-dismiss="modal">&nbsp;</button>
</div>
<div class="modal-body">
<div class="modal-body has-tabs">
<div class="alert alert-danger mb-0 rounded-0" id="le-error-info" role="alert"></div>
<form>
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="form-group">
<label class="form-label"><%- i18n('streams', 'incoming-port') %> <span class="form-required">*</span></label>
<input name="incoming_port" type="number" class="form-control text-monospace" placeholder="eg: 8080" min="1" max="65535" value="<%- incoming_port %>" required>
<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>
<li role="presentation" class="nav-item"><a href="#ssl-options" aria-controls="tab2" role="tab" data-toggle="tab" class="nav-link"><i class="fe fe-shield"></i> <%- i18n('str', 'ssl') %></a></li>
</ul>
<div class="tab-content">
<!-- Details -->
<div role="tabpanel" class="tab-pane active" id="details">
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="form-group">
<label class="form-label"><%- i18n('streams', 'incoming-port') %> <span class="form-required">*</span></label>
<input name="incoming_port" type="number" class="form-control text-monospace" placeholder="eg: 8080" min="1" max="65535" value="<%- incoming_port %>" required>
</div>
</div>
<div class="col-sm-8 col-md-8">
<div class="form-group">
<label class="form-label"><%- i18n('streams', 'forwarding-host') %><span class="form-required">*</span></label>
<input type="text" name="forwarding_host" class="form-control text-monospace" placeholder="example.com or 10.0.0.1 or 2001:db8:3333:4444:5555:6666:7777:8888" value="<%- forwarding_host %>" autocomplete="off" maxlength="255" required>
</div>
</div>
<div class="col-sm-4 col-md-4">
<div class="form-group">
<label class="form-label"><%- i18n('streams', 'forwarding-port') %> <span class="form-required">*</span></label>
<input name="forwarding_port" type="number" class="form-control text-monospace" placeholder="eg: 80" min="1" max="65535" value="<%- forwarding_port %>" 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="tcp_forwarding" value="1"<%- tcp_forwarding ? ' checked' : '' %>>
<span class="custom-switch-indicator"></span>
<span class="custom-switch-description"><%- i18n('streams', 'tcp-forwarding') %></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="udp_forwarding" value="1"<%- udp_forwarding ? ' checked' : '' %>>
<span class="custom-switch-indicator"></span>
<span class="custom-switch-description"><%- i18n('streams', 'udp-forwarding') %></span>
</label>
</div>
</div>
<div class="col-sm-12 col-md-12">
<div class="forward-type-error invalid-feedback"><%- i18n('streams', 'forward-type-error') %></div>
</div>
</div>
</div>
<div class="col-sm-8 col-md-8">
<div class="form-group">
<label class="form-label"><%- i18n('streams', 'forwarding-host') %><span class="form-required">*</span></label>
<input type="text" name="forwarding_host" class="form-control text-monospace" placeholder="example.com or 10.0.0.1 or 2001:db8:3333:4444:5555:6666:7777:8888" value="<%- forwarding_host %>" autocomplete="off" maxlength="255" required>
<!-- SSL -->
<div role="tabpanel" class="tab-pane" id="ssl-options">
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="form-group">
<label class="form-label"><%- i18n('streams', 'ssl-certificate') %></label>
<select name="certificate_id" class="form-control custom-select" placeholder="<%- i18n('all-hosts', 'none') %>">
<option selected value="0" data-data="{&quot;id&quot;:0}" <%- certificate_id ? '' : 'selected' %>><%- i18n('all-hosts', 'none') %></option>
<option selected value="new" data-data="{&quot;id&quot;:&quot;new&quot;}"><%- i18n('all-hosts', 'new-cert') %></option>
</select>
</div>
</div>
<!-- DNS challenge -->
<div class="col-sm-12 col-md-12 letsencrypt">
<div class="form-group">
<label class="form-label"><%- i18n('all-hosts', 'domain-names') %> <span class="form-required">*</span></label>
<input type="text" name="domain_names" class="form-control" id="input-domains" value="<%- domain_names.join(',') %>">
</div>
<div class="form-group">
<label class="custom-switch">
<input
type="checkbox"
class="custom-switch-input"
name="meta[dns_challenge]"
value="1"
checked
disabled
>
<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.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">
<label class="form-label"><%- i18n('ssl', 'letsencrypt-email') %> <span class="form-required">*</span></label>
<input name="meta[letsencrypt_email]" type="email" class="form-control" placeholder="" value="<%- getLetsencryptEmail() %>" required disabled>
</div>
</div>
<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[letsencrypt_agree]" value="1" required disabled>
<span class="custom-switch-indicator"></span>
<span class="custom-switch-description"><%= i18n('ssl', 'letsencrypt-agree', {url: 'https://letsencrypt.org/repository/'}) %> <span class="form-required">*</span></span>
</label>
</div>
</div>
</div>
</div>
<div class="col-sm-4 col-md-4">
<div class="form-group">
<label class="form-label"><%- i18n('streams', 'forwarding-port') %> <span class="form-required">*</span></label>
<input name="forwarding_port" type="number" class="form-control text-monospace" placeholder="eg: 80" min="1" max="65535" value="<%- forwarding_port %>" 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="tcp_forwarding" value="1"<%- tcp_forwarding ? ' checked' : '' %>>
<span class="custom-switch-indicator"></span>
<span class="custom-switch-description"><%- i18n('streams', 'tcp-forwarding') %></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="udp_forwarding" value="1"<%- udp_forwarding ? ' checked' : '' %>>
<span class="custom-switch-indicator"></span>
<span class="custom-switch-description"><%- i18n('streams', 'udp-forwarding') %></span>
</label>
</div>
</div>
<div class="col-sm-12 col-md-12">
<div class="forward-type-error invalid-feedback"><%- i18n('streams', 'forward-type-error') %></div>
</div>
</div>
</form>
</div>

View File

@ -1,24 +1,38 @@
const Mn = require('backbone.marionette');
const App = require('../../main');
const StreamModel = require('../../../models/stream');
const template = require('./form.ejs');
const Mn = require('backbone.marionette');
const App = require('../../main');
const StreamModel = require('../../../models/stream');
const template = require('./form.ejs');
const dns_providers = require('../../../../../global/certbot-dns-plugins');
require('jquery-serializejson');
require('jquery-mask-plugin');
require('selectize');
const Helpers = require("../../../lib/helpers");
const certListItemTemplate = require("../certificates-list-item.ejs");
const i18n = require("../../i18n");
module.exports = Mn.View.extend({
template: template,
className: 'modal-dialog',
ui: {
form: 'form',
forwarding_host: 'input[name="forwarding_host"]',
type_error: '.forward-type-error',
buttons: '.modal-footer button',
switches: '.custom-switch-input',
cancel: 'button.cancel',
save: 'button.save'
form: 'form',
forwarding_host: 'input[name="forwarding_host"]',
type_error: '.forward-type-error',
buttons: '.modal-footer button',
switches: '.custom-switch-input',
cancel: 'button.cancel',
save: 'button.save',
le_error_info: '#le-error-info',
certificate_select: 'select[name="certificate_id"]',
domain_names: 'input[name="domain_names"]',
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: {
@ -48,6 +62,35 @@ module.exports = Mn.View.extend({
data.tcp_forwarding = !!data.tcp_forwarding;
data.udp_forwarding = !!data.udp_forwarding;
if (typeof data.meta === 'undefined') data.meta = {};
data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1;
data.meta.dns_challenge = true;
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') {
let domain_err = false;
if (!data.meta.dns_challenge) {
data.domain_names.map(function (name) {
if (name.match(/\*/im)) {
domain_err = true;
}
});
}
if (domain_err) {
alert(i18n('ssl', 'no-wildcard-without-dns'));
return;
}
} else {
data.certificate_id = parseInt(data.certificate_id, 10);
}
let method = App.Api.Nginx.Streams.create;
let is_new = true;
@ -70,10 +113,108 @@ 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');
});
}
},
'change @ui.certificate_select': function () {
let id = this.ui.certificate_select.val();
if (id === 'new') {
this.ui.letsencrypt.show().find('input').prop('disabled', false);
this.ui.domain_names.prop('required', 'required');
this.ui.dns_challenge_switch
.prop('disabled', true)
.parents('.form-group')
.css('opacity', 0.5);
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.letsencrypt.hide().find('input').prop('disabled', true);
}
},
'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();
}
},
},
templateContext: {
getLetsencryptEmail: function () {
return App.Cache.User.get('email');
},
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 () {
let view = this;
// 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',
labelField: 'nice_name',
searchField: ['nice_name', 'domain_names'],
create: false,
preload: true,
allowEmptyOption: true,
render: {
option: function (item) {
item.i18n = App.i18n;
item.formatDbDate = Helpers.formatDbDate;
return certListItemTemplate(item);
}
},
load: function (query, callback) {
App.Api.Nginx.Certificates.getAll()
.then(rows => {
callback(rows);
})
.catch(err => {
console.error(err);
callback();
});
},
onLoad: function () {
view.ui.certificate_select[0].selectize.setValue(view.model.get('certificate_id'));
}
});
},
initialize: function (options) {

View File

@ -1,6 +1,6 @@
<td class="text-center">
<div class="avatar d-block" style="background-image: url(<%- owner.avatar || '/images/default-avatar.jpg' %>)" title="Owned by <%- owner.name %>">
<span class="avatar-status <%- owner.is_disabled ? 'bg-red' : 'bg-green' %>"></span>
<div class="avatar d-block" style="background-image: url(<%- (owner && owner.avatar) || '/images/default-avatar.jpg' %>)" title="Owned by <%- (owner && owner.name) || 'a deleted user' %>">
<span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span>
</div>
</td>
<td>
@ -16,7 +16,10 @@
</td>
<td>
<div>
<% if (tcp_forwarding) { %>
<% if (certificate) { %>
<span class="tag"><%- i18n('streams', 'tcp+ssl') %></span>
<% }
else if (tcp_forwarding) { %>
<span class="tag"><%- i18n('streams', 'tcp') %></span>
<% }
if (udp_forwarding) { %>
@ -24,6 +27,9 @@
<% } %>
</div>
</td>
<td>
<div><%- certificate && certificate_id ? i18n('ssl', certificate.provider) : i18n('all-hosts', 'none') %></div>
</td>
<td>
<%
var o = isOnline();

View File

@ -3,6 +3,7 @@
<th><%- i18n('streams', 'incoming-port') %></th>
<th><%- i18n('str', 'destination') %></th>
<th><%- i18n('streams', 'protocol') %></th>
<th><%- i18n('str', 'ssl') %></th>
<th><%- i18n('str', 'status') %></th>
<% if (canManage) { %>
<th>&nbsp;</th>

View File

@ -88,7 +88,7 @@ module.exports = Mn.View.extend({
onRender: function () {
let view = this;
view.fetch(['owner'])
view.fetch(['owner', 'certificate'])
.then(response => {
if (!view.isDestroyed()) {
if (response && response.length) {

View File

@ -1,10 +1,10 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><%- i18n('users', 'form-title', {id: id}) %></h5>
<button type="button" class="close cancel" aria-label="Close" data-dismiss="modal">&nbsp;</button>
</div>
<div class="modal-body">
<form>
<form>
<div class="modal-header">
<h5 class="modal-title"><%- i18n('users', 'form-title', {id: id}) %></h5>
<button type="button" class="close cancel" aria-label="Close" data-dismiss="modal">&nbsp;</button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-sm-6 col-md-6">
<div class="form-group">
@ -49,10 +49,10 @@
</div>
<% } %>
</div>
</form>
</div>
<div class="modal-footer">
<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>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'cancel') %></button>
<button type="submit" class="btn btn-teal save"><%- i18n('str', 'save') %></button>
</div>
</form>
</div>

View File

@ -19,7 +19,7 @@ module.exports = Mn.View.extend({
events: {
'click @ui.save': function (e) {
'submit @ui.form': function (e) {
e.preventDefault();
this.ui.error.hide();
let view = this;

View File

@ -179,7 +179,9 @@
"delete-confirm": "Are you sure you want to delete this Stream?",
"help-title": "What is a Stream?",
"help-content": "A relatively new feature for Nginx, a Stream will serve to forward TCP/UDP traffic directly to another computer on the network.\nIf you're running game servers, FTP or SSH servers this can come in handy.",
"search": "Search Incoming Port…"
"search": "Search Incoming Port…",
"ssl-certificate": "SSL Certificate for TCP Forwarding",
"tcp+ssl": "TCP+SSL"
},
"certificates": {
"title": "SSL Certificates",
@ -206,7 +208,10 @@
"reachability-other": "There is a server found at this domain but it returned an unexpected status code {code}. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running.",
"download": "Download",
"renew-title": "Renew Let's Encrypt Certificate",
"search": "Search Certificate…"
"search": "Search Certificate…",
"in-use" : "In use",
"inactive": "Inactive",
"active-domain_names": "Active domain names"
},
"access-lists": {
"title": "Access Lists",

View File

@ -15,8 +15,11 @@ const model = Backbone.Model.extend({
udp_forwarding: false,
enabled: true,
meta: {},
certificate_id: 0,
domain_names: [],
// The following are expansions:
owner: null
owner: null,
certificate: null
};
}
});

View File

@ -2648,9 +2648,9 @@ electron-to-chromium@^1.3.47:
integrity sha512-67V62Z4CFOiAtox+o+tosGfVk0QX4DJgH609tjT8QymbJZVAI/jWnAthnr8c5hnRNziIRwkc9EMQYejiVz3/9Q==
elliptic@^6.5.3, elliptic@^6.5.4:
version "6.5.7"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.7.tgz#8ec4da2cb2939926a1b9a73619d768207e647c8b"
integrity sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==
version "6.6.0"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.6.0.tgz#5919ec723286c1edf28685aa89261d4761afa210"
integrity sha512-dpwoQcLc/2WLQvJvLRHKZ+f9FgOdjnq11rurqwekGQygGPsYSK29OMMD2WalatiqQ+XGFDglTNixpPfI+lpaAA==
dependencies:
bn.js "^4.11.9"
brorand "^1.1.0"

View File

@ -7,7 +7,7 @@
"credentials": "dns_acmedns_api_url = http://acmedns-server/\ndns_acmedns_registration_file = /data/acme-registration.json",
"full_plugin_name": "dns-acmedns"
},
"active24":{
"active24": {
"name": "Active24",
"package_name": "certbot-dns-active24",
"version": "~=1.5.1",
@ -18,7 +18,7 @@
"aliyun": {
"name": "Aliyun",
"package_name": "certbot-dns-aliyun",
"version": "~=0.38.1",
"version": "~=2.0.0",
"dependencies": "",
"credentials": "dns_aliyun_access_key = 12345678\ndns_aliyun_access_key_secret = 1234567890abcdef1234567890abcdef",
"full_plugin_name": "dns-aliyun"
@ -31,6 +31,14 @@
"credentials": "# This plugin supported API authentication using either Service Principals or utilizing a Managed Identity assigned to the virtual machine.\n# Regardless which authentication method used, the identity will need the “DNS Zone Contributor” role assigned to it.\n# As multiple Azure DNS Zones in multiple resource groups can exist, the config file needs a mapping of zone to resource group ID. Multiple zones -> ID mappings can be listed by using the key dns_azure_zoneX where X is a unique number. At least 1 zone mapping is required.\n\n# Using a service principal (option 1)\ndns_azure_sp_client_id = 912ce44a-0156-4669-ae22-c16a17d34ca5\ndns_azure_sp_client_secret = E-xqXU83Y-jzTI6xe9fs2YC~mck3ZzUih9\ndns_azure_tenant_id = ed1090f3-ab18-4b12-816c-599af8a88cf7\n\n# Using used assigned MSI (option 2)\n# dns_azure_msi_client_id = 912ce44a-0156-4669-ae22-c16a17d34ca5\n\n# Using system assigned MSI (option 3)\n# dns_azure_msi_system_assigned = true\n\n# Zones (at least one always required)\ndns_azure_zone1 = example.com:/subscriptions/c135abce-d87d-48df-936c-15596c6968a5/resourceGroups/dns1\ndns_azure_zone2 = example.org:/subscriptions/99800903-fb14-4992-9aff-12eaf2744622/resourceGroups/dns2",
"full_plugin_name": "dns-azure"
},
"beget": {
"name":"Beget",
"package_name": "certbot-beget-plugin",
"version": "~=1.0.0.dev9",
"dependencies": "",
"credentials": "# Beget API credentials used by Certbot\nbeget_plugin_username = username\nbeget_plugin_password = password",
"full_plugin_name": "beget-plugin"
},
"bunny": {
"name": "bunny.net",
"package_name": "certbot-dns-bunny",
@ -153,11 +161,11 @@
},
"domainoffensive": {
"name": "DomainOffensive (do.de)",
"package_name": "certbot-dns-do",
"version": "~=0.31.0",
"package_name": "certbot-dns-domainoffensive",
"version": "~=2.0.0",
"dependencies": "",
"credentials": "dns_do_api_token = YOUR_DO_DE_AUTH_TOKEN",
"full_plugin_name": "dns-do"
"full_plugin_name": "dns-domainoffensive"
},
"domeneshop": {
"name": "Domeneshop",
@ -207,6 +215,14 @@
"credentials": "# Gandi personal access token\ndns_gandi_token=PERSONAL_ACCESS_TOKEN",
"full_plugin_name": "dns-gandi"
},
"gcore": {
"name": "Gcore DNS",
"package_name": "certbot-dns-gcore",
"version": "~=0.1.8",
"dependencies": "",
"credentials": "dns_gcore_apitoken = 0123456789abcdef0123456789abcdef01234567",
"full_plugin_name": "dns-gcore"
},
"godaddy": {
"name": "GoDaddy",
"package_name": "certbot-dns-godaddy",
@ -247,6 +263,14 @@
"credentials": "dns_hetzner_api_token = 0123456789abcdef0123456789abcdef",
"full_plugin_name": "dns-hetzner"
},
"hostingnl": {
"name": "Hosting.nl",
"package_name": "certbot-dns-hostingnl",
"version": "~=0.1.5",
"dependencies": "",
"credentials": "dns_hostingnl_api_key = 0123456789abcdef0123456789abcdef",
"full_plugin_name": "dns-hostingnl"
},
"hover": {
"name": "Hover",
"package_name": "certbot-dns-hover",
@ -303,6 +327,14 @@
"credentials": "dns_joker_username = <Dynamic DNS Authentication Username>\ndns_joker_password = <Dynamic DNS Authentication Password>\ndns_joker_domain = <Dynamic DNS Domain>",
"full_plugin_name": "dns-joker"
},
"leaseweb": {
"name": "LeaseWeb",
"package_name": "certbot-dns-leaseweb",
"version": "~=1.0.1",
"dependencies": "",
"credentials": "dns_leaseweb_api_token = 01234556789",
"full_plugin_name": "dns-leaseweb"
},
"linode": {
"name": "Linode",
"package_name": "certbot-dns-linode",
@ -332,7 +364,7 @@
"package_name": "certbot-dns-mijn-host",
"version": "~=0.0.4",
"dependencies": "",
"credentials": "dns-mijn-host-credentials = /etc/letsencrypt/mijnhost-credentials.ini",
"credentials": "dns_mijn_host_api_key=0123456789abcdef0123456789abcdef",
"full_plugin_name": "dns-mijn-host"
},
"namecheap": {
@ -394,7 +426,7 @@
"porkbun": {
"name": "Porkbun",
"package_name": "certbot-dns-porkbun",
"version": "~=0.2",
"version": "~=0.9",
"dependencies": "",
"credentials": "dns_porkbun_key=your-porkbun-api-key\ndns_porkbun_secret=your-porkbun-api-secret",
"full_plugin_name": "dns-porkbun"
@ -424,13 +456,13 @@
"full_plugin_name": "dns-rfc2136"
},
"rockenstein": {
"name": "rockenstein AG",
"package_name": "certbot-dns-rockenstein",
"version": "~=1.0.0",
"dependencies": "",
"credentials": "dns_rockenstein_token=<token>",
"full_plugin_name": "dns-rockenstein"
},
"name": "rockenstein AG",
"package_name": "certbot-dns-rockenstein",
"version": "~=1.0.0",
"dependencies": "",
"credentials": "dns_rockenstein_token=<token>",
"full_plugin_name": "dns-rockenstein"
},
"route53": {
"name": "Route 53 (Amazon)",
"package_name": "certbot-dns-route53",
@ -487,7 +519,7 @@
"credentials": "dns_websupport_identifier = <api_key>\ndns_websupport_secret_key = <secret>",
"full_plugin_name": "dns-websupport"
},
"wedos":{
"wedos": {
"name": "Wedos",
"package_name": "certbot-dns-wedos",
"version": "~=2.2",
@ -502,5 +534,13 @@
"dependencies": "",
"credentials": "edgedns_client_secret = as3d1asd5d1a32sdfsdfs2d1asd5=\nedgedns_host = sdflskjdf-dfsdfsdf-sdfsdfsdf.luna.akamaiapis.net\nedgedns_access_token = kjdsi3-34rfsdfsdf-234234fsdfsdf\nedgedns_client_token = dkfjdf-342fsdfsd-23fsdfsdfsdf",
"full_plugin_name": "edgedns"
}
},
"zoneedit": {
"name": "ZoneEdit",
"package_name": "certbot-dns-zoneedit",
"version": "~=0.3.2",
"dependencies": "--no-deps dnspython",
"credentials": "dns_zoneedit_user = <login-user-id>\ndns_zoneedit_token = <dyn-authentication-token>",
"full_plugin_name": "dns-zoneedit"
}
}

View File

@ -11,7 +11,7 @@ YELLOW='\E[1;33m'
export BLUE CYAN GREEN RED RESET YELLOW
# Docker Compose
COMPOSE_PROJECT_NAME="npmdev"
COMPOSE_PROJECT_NAME="npm2dev"
COMPOSE_FILE="docker/docker-compose.dev.yml"
export COMPOSE_FILE COMPOSE_PROJECT_NAME

View File

@ -67,6 +67,8 @@ printf "nameserver %s\noptions ndots:0" "${DNSROUTER_IP}" > "${LOCAL_RESOLVE}"
# bring up all remaining containers, except cypress!
docker-compose up -d --remove-orphans stepca squid
docker-compose pull db-mysql || true # ok to fail
docker-compose pull db-postgres || true # ok to fail
docker-compose pull authentik authentik-redis authentik-ldap || true # ok to fail
docker-compose up -d --remove-orphans --pull=never fullstack
# wait for main container to be healthy

View File

@ -36,12 +36,11 @@ if hash docker-compose 2>/dev/null; then
# bring up all remaining containers, except cypress!
docker-compose up -d --remove-orphans stepca squid
docker-compose pull db
docker-compose up -d --remove-orphans --pull=never fullstack
docker-compose pull db db-postgres authentik-redis authentik authentik-worker authentik-ldap
docker-compose build --pull --parallel fullstack
docker-compose up -d --remove-orphans fullstack
docker-compose up -d --remove-orphans swagger
# docker-compose up -d --remove-orphans --force-recreate --build
# wait for main container to be healthy
bash "$DIR/wait-healthy" "$(docker-compose ps --all -q fullstack)" 120
@ -53,10 +52,10 @@ if hash docker-compose 2>/dev/null; then
if [ "$1" == "-f" ]; then
echo -e "${BLUE} ${YELLOW}Following Backend Container:${RESET}"
docker logs -f npm_core
docker logs -f npm2dev.core
else
echo -e "${YELLOW}Hint:${RESET} You can follow the output of some of the containers with:"
echo " docker logs -f npm_core"
echo " docker logs -f npm2dev.core"
fi
else
echo -e "${RED} docker-compose command is not available${RESET}"

View File

@ -1,11 +1,22 @@
FROM cypress/included:13.9.0
COPY --chown=1000 ./test /test
FROM cypress/included:14.0.1
# Disable Cypress CLI colors
ENV FORCE_COLOR=0
ENV NO_COLOR=1
# testssl.sh and mkcert
RUN wget "https://github.com/testssl/testssl.sh/archive/refs/tags/v3.2rc4.tar.gz" -O /tmp/testssl.tgz -q \
&& tar -xzf /tmp/testssl.tgz -C /tmp \
&& mv /tmp/testssl.sh-3.2rc4 /testssl \
&& rm /tmp/testssl.tgz \
&& apt-get update \
&& apt-get install -y bsdmainutils curl dnsutils \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* \
&& wget "https://github.com/FiloSottile/mkcert/releases/download/v1.4.4/mkcert-v1.4.4-linux-amd64" -O /bin/mkcert \
&& chmod +x /bin/mkcert
COPY --chown=1000 ./test /test
WORKDIR /test
RUN yarn install && yarn cache clean
ENTRYPOINT []

View File

@ -0,0 +1,25 @@
/// <reference types="cypress" />
describe('Dashboard endpoints', () => {
let token;
before(() => {
cy.getToken().then((tok) => {
token = tok;
});
});
it('Should be able to get host counts', function() {
cy.task('backendApiGet', {
token: token,
path: '/api/reports/hosts'
}).then((data) => {
cy.validateSwaggerSchema('get', 200, '/reports/hosts', data);
expect(data).to.have.property('dead');
expect(data).to.have.property('proxy');
expect(data).to.have.property('redirection');
expect(data).to.have.property('stream');
});
});
});

View File

@ -0,0 +1,64 @@
/// <reference types="cypress" />
describe('LDAP with Authentik', () => {
let token;
if (Cypress.env('skipStackCheck') === 'true' || Cypress.env('stack') === 'postgres') {
before(() => {
cy.getToken().then((tok) => {
token = tok;
// cy.task('backendApiPut', {
// token: token,
// path: '/api/settings/ldap-auth',
// data: {
// value: {
// host: 'authentik-ldap:3389',
// base_dn: 'ou=users,DC=ldap,DC=goauthentik,DC=io',
// user_dn: 'cn={{USERNAME}},ou=users,DC=ldap,DC=goauthentik,DC=io',
// email_property: 'mail',
// name_property: 'sn',
// self_filter: '(&(cn={{USERNAME}})(ak-active=TRUE))',
// auto_create_user: true
// }
// }
// }).then((data) => {
// cy.validateSwaggerSchema('put', 200, '/settings/{name}', data);
// expect(data.result).to.have.property('id');
// expect(data.result.id).to.be.greaterThan(0);
// });
// cy.task('backendApiPut', {
// token: token,
// path: '/api/settings/auth-methods',
// data: {
// value: [
// 'local',
// 'ldap'
// ]
// }
// }).then((data) => {
// cy.validateSwaggerSchema('put', 200, '/settings/{name}', data);
// expect(data.result).to.have.property('id');
// expect(data.result.id).to.be.greaterThan(0);
// });
});
});
it.skip('Should log in with LDAP', function() {
// cy.task('backendApiPost', {
// token: token,
// path: '/api/auth',
// data: {
// // Authentik LDAP creds:
// type: 'ldap',
// identity: 'cypress',
// secret: 'fqXBfUYqHvYqiwBHWW7f'
// }
// }).then((data) => {
// cy.validateSwaggerSchema('post', 200, '/auth', data);
// expect(data.result).to.have.property('token');
// });
});
}
});

View File

@ -0,0 +1,97 @@
/// <reference types="cypress" />
describe('OAuth with Authentik', () => {
let token;
if (Cypress.env('skipStackCheck') === 'true' || Cypress.env('stack') === 'postgres') {
before(() => {
cy.getToken().then((tok) => {
token = tok;
// cy.task('backendApiPut', {
// token: token,
// path: '/api/settings/oauth-auth',
// data: {
// value: {
// client_id: '7iO2AvuUp9JxiSVkCcjiIbQn4mHmUMBj7yU8EjqU',
// client_secret: 'VUMZzaGTrmXJ8PLksyqzyZ6lrtz04VvejFhPMBP9hGZNCMrn2LLBanySs4ta7XGrDr05xexPyZT1XThaf4ubg00WqvHRVvlu4Naa1aMootNmSRx3VAk6RSslUJmGyHzq',
// authorization_url: 'http://authentik:9000/application/o/authorize/',
// resource_url: 'http://authentik:9000/application/o/userinfo/',
// token_url: 'http://authentik:9000/application/o/token/',
// logout_url: 'http://authentik:9000/application/o/npm/end-session/',
// identifier: 'preferred_username',
// scopes: [],
// auto_create_user: true
// }
// }
// }).then((data) => {
// cy.validateSwaggerSchema('put', 200, '/settings/{name}', data);
// expect(data.result).to.have.property('id');
// expect(data.result.id).to.be.greaterThan(0);
// });
// cy.task('backendApiPut', {
// token: token,
// path: '/api/settings/auth-methods',
// data: {
// value: [
// 'local',
// 'oauth'
// ]
// }
// }).then((data) => {
// cy.validateSwaggerSchema('put', 200, '/settings/{name}', data);
// expect(data.result).to.have.property('id');
// expect(data.result.id).to.be.greaterThan(0);
// });
});
});
it.skip('Should log in with OAuth', function() {
// cy.task('backendApiGet', {
// path: '/oauth/login?redirect_base=' + encodeURI(Cypress.config('baseUrl')),
// }).then((data) => {
// expect(data).to.have.property('result');
// cy.origin('http://authentik:9000', {args: data.result}, (url) => {
// cy.visit(url);
// cy.get('ak-flow-executor')
// .shadow()
// .find('ak-stage-identification')
// .shadow()
// .find('input[name="uidField"]', { visible: true })
// .type('cypress');
// cy.get('ak-flow-executor')
// .shadow()
// .find('ak-stage-identification')
// .shadow()
// .find('button[type="submit"]', { visible: true })
// .click();
// cy.get('ak-flow-executor')
// .shadow()
// .find('ak-stage-password')
// .shadow()
// .find('input[name="password"]', { visible: true })
// .type('fqXBfUYqHvYqiwBHWW7f');
// cy.get('ak-flow-executor')
// .shadow()
// .find('ak-stage-password')
// .shadow()
// .find('button[type="submit"]', { visible: true })
// .click();
// })
// // we should be logged in
// cy.get('#root p.chakra-text')
// .first()
// .should('have.text', 'Nginx Proxy Manager');
// // logout:
// cy.clearLocalStorage();
// });
});
}
});

View File

@ -0,0 +1,213 @@
/// <reference types="cypress" />
describe('Streams', () => {
let token;
before(() => {
cy.getToken().then((tok) => {
token = tok;
// Set default site content
cy.task('backendApiPut', {
token: token,
path: '/api/settings/default-site',
data: {
value: 'html',
meta: {
html: '<p>yay it works</p>'
},
},
}).then((data) => {
cy.validateSwaggerSchema('put', 200, '/settings/{settingID}', data);
});
});
// Create a custom cert pair
cy.exec('mkcert -cert-file=/test/cypress/fixtures/website1.pem -key-file=/test/cypress/fixtures/website1.key.pem website1.example.com').then((result) => {
expect(result.code).to.eq(0);
// Install CA
cy.exec('mkcert -install').then((result) => {
expect(result.code).to.eq(0);
});
});
cy.exec('rm -f /test/results/testssl.json');
});
it('Should be able to create TCP Stream', function() {
cy.task('backendApiPost', {
token: token,
path: '/api/nginx/streams',
data: {
incoming_port: 1500,
forwarding_host: '127.0.0.1',
forwarding_port: 80,
certificate_id: 0,
meta: {
dns_provider_credentials: "",
letsencrypt_agree: false,
dns_challenge: true
},
tcp_forwarding: true,
udp_forwarding: false
}
}).then((data) => {
cy.validateSwaggerSchema('post', 201, '/nginx/streams', data);
expect(data).to.have.property('id');
expect(data.id).to.be.greaterThan(0);
expect(data).to.have.property('enabled', true);
expect(data).to.have.property('tcp_forwarding', true);
expect(data).to.have.property('udp_forwarding', false);
cy.exec('curl --noproxy -- http://website1.example.com:1500').then((result) => {
expect(result.code).to.eq(0);
expect(result.stdout).to.contain('yay it works');
});
});
});
it('Should be able to create UDP Stream', function() {
cy.task('backendApiPost', {
token: token,
path: '/api/nginx/streams',
data: {
incoming_port: 1501,
forwarding_host: '127.0.0.1',
forwarding_port: 80,
certificate_id: 0,
meta: {
dns_provider_credentials: "",
letsencrypt_agree: false,
dns_challenge: true
},
tcp_forwarding: false,
udp_forwarding: true
}
}).then((data) => {
cy.validateSwaggerSchema('post', 201, '/nginx/streams', data);
expect(data).to.have.property('id');
expect(data.id).to.be.greaterThan(0);
expect(data).to.have.property('enabled', true);
expect(data).to.have.property('tcp_forwarding', false);
expect(data).to.have.property('udp_forwarding', true);
});
});
it('Should be able to create TCP/UDP Stream', function() {
cy.task('backendApiPost', {
token: token,
path: '/api/nginx/streams',
data: {
incoming_port: 1502,
forwarding_host: '127.0.0.1',
forwarding_port: 80,
certificate_id: 0,
meta: {
dns_provider_credentials: "",
letsencrypt_agree: false,
dns_challenge: true
},
tcp_forwarding: true,
udp_forwarding: true
}
}).then((data) => {
cy.validateSwaggerSchema('post', 201, '/nginx/streams', data);
expect(data).to.have.property('id');
expect(data.id).to.be.greaterThan(0);
expect(data).to.have.property('enabled', true);
expect(data).to.have.property('tcp_forwarding', true);
expect(data).to.have.property('udp_forwarding', true);
cy.exec('curl --noproxy -- http://website1.example.com:1502').then((result) => {
expect(result.code).to.eq(0);
expect(result.stdout).to.contain('yay it works');
});
});
});
it('Should be able to create SSL TCP Stream', function() {
let certID = 0;
// Create custom cert
cy.task('backendApiPost', {
token: token,
path: '/api/nginx/certificates',
data: {
provider: "other",
nice_name: "Custom Certificate for SSL Stream",
},
}).then((data) => {
cy.validateSwaggerSchema('post', 201, '/nginx/certificates', data);
expect(data).to.have.property('id');
certID = data.id;
// Upload files
cy.task('backendApiPostFiles', {
token: token,
path: `/api/nginx/certificates/${certID}/upload`,
files: {
certificate: 'website1.pem',
certificate_key: 'website1.key.pem',
},
}).then((data) => {
cy.validateSwaggerSchema('post', 200, '/nginx/certificates/{certID}/upload', data);
expect(data).to.have.property('certificate');
expect(data).to.have.property('certificate_key');
// Create the stream
cy.task('backendApiPost', {
token: token,
path: '/api/nginx/streams',
data: {
incoming_port: 1503,
forwarding_host: '127.0.0.1',
forwarding_port: 80,
certificate_id: certID,
meta: {
dns_provider_credentials: "",
letsencrypt_agree: false,
dns_challenge: true
},
tcp_forwarding: true,
udp_forwarding: false
}
}).then((data) => {
cy.validateSwaggerSchema('post', 201, '/nginx/streams', data);
expect(data).to.have.property('id');
expect(data.id).to.be.greaterThan(0);
expect(data).to.have.property("enabled", true);
expect(data).to.have.property('tcp_forwarding', true);
expect(data).to.have.property('udp_forwarding', false);
expect(data).to.have.property('certificate_id', certID);
// Check the ssl termination
cy.task('log', '[testssl.sh] Running ...');
cy.exec('/testssl/testssl.sh --quiet --add-ca="$(/bin/mkcert -CAROOT)/rootCA.pem" --jsonfile=/test/results/testssl.json website1.example.com:1503', {
timeout: 120000, // 2 minutes
}).then((result) => {
cy.task('log', '[testssl.sh] ' + result.stdout);
const allowedSeverities = ["INFO", "OK", "LOW", "MEDIUM"];
const ignoredIDs = [
'cert_chain_of_trust',
'cert_extlifeSpan',
'cert_revocation',
'overall_grade',
];
cy.readFile('/test/results/testssl.json').then((data) => {
// Parse each array item
for (let i = 0; i < data.length; i++) {
const item = data[i];
if (ignoredIDs.includes(item.id)) {
continue;
}
expect(item.severity).to.be.oneOf(allowedSeverities);
}
});
});
});
});
});
});
});

View File

@ -4,18 +4,18 @@
"description": "",
"main": "index.js",
"dependencies": {
"@jc21/cypress-swagger-validation": "^0.3.1",
"axios": "^1.7.7",
"cypress": "^13.15.0",
"cypress-multi-reporters": "^1.6.4",
"@jc21/cypress-swagger-validation": "^0.3.2",
"axios": "^1.7.9",
"cypress": "^14.0.1",
"cypress-multi-reporters": "^2.0.5",
"cypress-wait-until": "^3.0.2",
"eslint": "^9.12.0",
"eslint": "^9.19.0",
"eslint-plugin-align-assignments": "^1.1.2",
"eslint-plugin-chai-friendly": "^1.0.1",
"eslint-plugin-cypress": "^3.5.0",
"eslint-plugin-cypress": "^4.1.0",
"form-data": "^4.0.1",
"lodash": "^4.17.21",
"mocha": "^10.7.3",
"mocha": "^11.1.0",
"mocha-junit-reporter": "^2.2.1"
},
"scripts": {

View File

@ -132,9 +132,9 @@
integrity sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==
"@eslint/plugin-kit@^0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz#8712dccae365d24e9eeecb7b346f85e750ba343d"
integrity sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==
version "0.2.3"
resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz#812980a6a41ecf3a8341719f92a6d1e784a2e0e8"
integrity sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==
dependencies:
levn "^0.4.1"
@ -628,9 +628,9 @@ core-util-is@1.0.2:
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
cross-spawn@^7.0.0, cross-spawn@^7.0.2:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
version "7.0.6"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
dependencies:
path-key "^3.1.0"
shebang-command "^2.0.0"