Compare commits

...

715 Commits

Author SHA1 Message Date
747de511d4 Sets certbot official dns plugin version requirement to match certbot version 2021-12-29 09:54:59 +01:00
adc5a2020a Merge pull request #1666 from TobiasKneidl/patch-1
Update default.conf to follow the default site setting also for ipv6
2021-12-27 11:03:14 +10:00
40b1521f72 Merge pull request #1677 from jc21/fixes-custom-certificate-modal
Fixes custom certificate upload modal
2021-12-27 11:02:16 +10:00
ac23c66659 Fixes custom certificate upload modal 2021-12-23 12:23:30 +01:00
6392df36c3 Merge branch 'master' into develop 2021-12-22 11:52:27 +10:00
c82843279c Bump version 2021-12-22 11:51:32 +10:00
b394eb8e55 Hack for python binary in newer node images 2021-12-22 11:05:57 +10:00
bb422d4454 Update default.conf 2021-12-22 00:24:05 +01:00
d45f39aae3 Merge pull request #1538 from jc21/adds-http-challenge-test
Adds buttons to test availability of server from public internet
2021-11-10 09:15:26 +10:00
cb091040a8 Merge pull request #1578 from jc21/undo-switch-to-variables
Reverts back to proxy_pass without variables
2021-11-10 09:13:02 +10:00
ddd538944a Skip migration if setting does not exist 2021-11-09 14:03:39 +01:00
1f879f67a9 Reverts back to proxy_pass without variables 2021-11-09 13:57:39 +01:00
ee89dedd0f Adds migration to regenerate default host 2021-11-08 23:11:56 +01:00
9ab5333652 Merge pull request #1574 from jc21/develop
v2.9.12
2021-11-08 14:34:31 +10:00
3bd97ae1b0 Version bump 2021-11-08 13:37:44 +10:00
432062e0f4 Merge pull request #1559 from jc21/variable-fix-custom-locations
Fixes custom location with capture groups
2021-11-08 12:29:43 +10:00
0c8bbb4bc2 Merge pull request #1561 from jc21/header-warning-for-advanced-config
Adds advanced config header warning
2021-11-08 12:28:34 +10:00
48e96c46d5 Merge pull request #1565 from jc21/removes-hsts-from-admin-ui
Removes HSTS from admin ui
2021-11-08 12:27:36 +10:00
25e9acf618 Merge pull request #1566 from jc21/cleans-dependencies
Removes unused dependencies
2021-11-08 12:27:04 +10:00
a517e80236 Merge pull request #1567 from jc21/removes-renew-delay
Removes random delay when renewing certificates with the renew now button
2021-11-08 12:26:34 +10:00
3d4d3bc73b Merge pull request #1572 from jc21/letsencrypt-config-file
Uses letsencrypt config file everywhere
2021-11-08 12:25:48 +10:00
cf4d1f73fa Uses letsencrypt config file everywhere 2021-11-06 22:31:13 +01:00
c203d1a0d8 Requires ~() in location to remove $request_uri and removes $request_uri if it is just a slash 2021-11-06 13:38:02 +01:00
5f29f6b039 Removes random delay when renewing certificates with the renew now button 2021-11-05 14:20:12 +01:00
f75b5b867b Fixes formatting 2021-11-05 14:01:50 +01:00
67463ca136 Removes unused dependencies 2021-11-05 13:59:19 +01:00
8db541f37f Removes HSTS from admin ui 2021-11-05 00:09:05 +01:00
a5229d0e92 Adds advanced config header warning and replaces variable info with translation 2021-11-04 12:01:01 +01:00
38ec0f9f95 Adds logs to backend when testing http challenge 2021-11-04 11:16:23 +01:00
3d80759a21 Renames the $upstream variables and does not append $request_ui if capture group exists in location 2021-11-04 10:08:15 +01:00
d95cd36b3e Merge pull request #1544 from piuswalter/patch-1
Fixed typo in i18n translation file
2021-11-03 09:42:49 +10:00
4c6b96ad5d Merge pull request #1546 from jc21/removes-swagger-containter
Removes swagger container
2021-11-03 09:41:41 +10:00
c3bef2867e Merge pull request #1547 from jc21/makes-sqlite-default
Changes documentation to make SQLite the default db
2021-11-03 09:41:02 +10:00
efc5bff2e1 Makes SQLite the default database in documentation 2021-11-02 12:24:23 +01:00
ffe3db8c08 Falls back to SQLite if no environment variables are provided 2021-11-02 12:22:39 +01:00
4ada0feae3 Removes swagger container and adds exposed port for DB in dev env 2021-11-02 11:33:22 +01:00
e17de6058e Fixed typo 2021-11-01 17:43:46 +01:00
9efe6cfb39 Minor fixes 2021-10-31 13:41:29 +01:00
c86a1a50bd Fixes formatting 2021-10-31 00:28:43 +02:00
c55476b196 Adds buttons to test availability of server from public internet 2021-10-31 00:19:18 +02:00
3b47decbb0 Merge pull request #1528 from jc21/develop
v2.9.11
2021-10-27 22:11:55 +10:00
d0bfa082e0 Bump version 2021-10-27 21:43:07 +10:00
6b7a8b009e Merge pull request #1519 from jc21/fixes-incorrect-proxy-path
Updates proxy paths
2021-10-27 21:42:18 +10:00
ca59e585d8 Uses variable in proxy_pass for normal proxy hosts 2021-10-25 14:58:02 +02:00
bbde7a108a Use variable with full uri in proxy pass 2021-10-25 14:48:22 +02:00
87731a8b5c Revert "Utilise variable for custom locations proxy_pass"
This reverts commit 6c1ae77a2a.
2021-10-25 14:27:37 +02:00
29d4bd4ccf Merge pull request #1517 from jc21/develop
v2.9.10
2021-10-25 15:48:43 +10:00
925ad90f91 Merge branch 'master' into develop 2021-10-25 14:26:47 +10:00
650ae61c43 Version bump 2021-10-25 11:35:44 +10:00
02f3f9704f Merge pull request #1480 from jc21/prefer-isrg-cert-chain
Sets the cert chain to prefer ISRG Root X1
2021-10-25 08:42:31 +10:00
da7c3057b4 Merge pull request #1481 from jc21/certificate-no-domain-sorting
Removes sorting of domain names when creating a certificate
2021-10-25 08:40:19 +10:00
040b45cafa Merge pull request #1496 from jc21/fixes-cache-busting
Adds cache busting to js bundles as well
2021-10-25 08:30:15 +10:00
8ece310b9f Merge pull request #1514 from jc21/adds-dns-websupportsk-challenge
Adds Webbsupport.sk dns plugin
2021-10-25 08:26:20 +10:00
96959db3c2 Merge pull request #1504 from jc21/removes-certbot-plugin-prefixes
Removes certbot plugin prefixes
2021-10-25 08:26:03 +10:00
6360100611 Adds Webbsupport.sk dns plugin 2021-10-24 22:14:59 +02:00
b833044cea Fixes formatting 2021-10-20 18:38:21 +02:00
97909830f5 Removes dns plugin prefixes 2021-10-20 18:16:54 +02:00
8ae2de2f49 Updates certbot-dns-ionos to newest version 2021-10-20 18:08:11 +02:00
bf7b659e89 Adds cache busting to js bundles as well 2021-10-17 19:15:35 +02:00
4e3c7749af Removes sorting of domain names when creating a certificate 2021-10-12 16:18:11 +02:00
f63441921f Sets the cert chain to prefer ISRG Root X1 2021-10-12 16:11:47 +02:00
725ba83606 Merge pull request #1443 from jc21/dependabot/npm_and_yarn/docs/ansi-regex-5.0.1
Bump ansi-regex from 5.0.0 to 5.0.1 in /docs
2021-10-12 10:36:48 +10:00
281906c0b5 Merge pull request #1476 from jc21/dependabot/npm_and_yarn/backend/objection-2.2.16
Bump objection from 2.2.2 to 2.2.16 in /backend
2021-10-12 10:36:34 +10:00
8ed121f43d Bump ansi-regex from 5.0.0 to 5.0.1 in /docs
Bumps [ansi-regex](https://github.com/chalk/ansi-regex) from 5.0.0 to 5.0.1.
- [Release notes](https://github.com/chalk/ansi-regex/releases)
- [Commits](https://github.com/chalk/ansi-regex/compare/v5.0.0...v5.0.1)

---
updated-dependencies:
- dependency-name: ansi-regex
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-11 01:57:51 +00:00
81a9cab2b3 Merge pull request #1464 from jc21/fixes-navigation-on-mobile
Adds toggle to header to display menu on mobile
2021-10-11 11:55:37 +10:00
8d98a417c5 Merge pull request #1469 from jc21/certbot-plugin-updates
Certbot plugin updates
2021-10-11 11:54:51 +10:00
6fa81b179b Bump objection from 2.2.2 to 2.2.16 in /backend
Bumps [objection](https://github.com/vincit/objection.js) from 2.2.2 to 2.2.16.
- [Release notes](https://github.com/vincit/objection.js/releases)
- [Commits](https://github.com/vincit/objection.js/commits/2.2.16)

---
updated-dependencies:
- dependency-name: objection
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-11 01:53:33 +00:00
9e169fbb42 Merge pull request #1474 from Djelibeybi/add-oci-dns
Add DNS provider for Oracle Cloud Infrastructure (OCI) DNS
2021-10-11 11:53:26 +10:00
27f84f880a Updated node-sass and sass-loader 2021-10-11 11:11:46 +10:00
0d9c941b4e Add support for Oracle Cloud Infrastructure (OCI) DNS
Signed-off-by: Avi Miller <avi.miller@oracle.com>
2021-10-10 08:49:12 +11:00
8865aa9c8c Fixes formatting 2021-10-07 17:39:18 +02:00
6d8c4218f1 Replaces fixed certbot plugin version with optional version requirements 2021-10-07 17:13:48 +02:00
c134a43337 Updates DirectAdmin plugin to prevent certbot downgrade 2021-10-06 17:58:07 +02:00
780759dc27 Adds toggle to header to display menu on mobile 2021-10-05 21:00:10 +02:00
85128f08f3 Merge pull request #1409 from jc21/dependabot/npm_and_yarn/docs/set-value-4.0.1
Bump set-value from 3.0.2 to 4.0.1 in /docs
2021-09-30 13:16:28 +10:00
d2f8c1e5f1 Merge pull request #1412 from jc21/dependabot/npm_and_yarn/docs/prismjs-1.25.0
Bump prismjs from 1.24.0 to 1.25.0 in /docs
2021-09-30 13:16:20 +10:00
9c88b9c1e9 Merge pull request #1415 from jc21/dependabot/npm_and_yarn/docs/nth-check-2.0.1
Bump nth-check from 1.0.2 to 2.0.1 in /docs
2021-09-30 13:16:10 +10:00
13fd2ce4e2 Bump nth-check from 1.0.2 to 2.0.1 in /docs
Bumps [nth-check](https://github.com/fb55/nth-check) from 1.0.2 to 2.0.1.
- [Release notes](https://github.com/fb55/nth-check/releases)
- [Commits](https://github.com/fb55/nth-check/compare/v1.0.2...v2.0.1)

---
updated-dependencies:
- dependency-name: nth-check
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-22 04:54:41 +00:00
9979f516d6 Bump prismjs from 1.24.0 to 1.25.0 in /docs
Bumps [prismjs](https://github.com/PrismJS/prism) from 1.24.0 to 1.25.0.
- [Release notes](https://github.com/PrismJS/prism/releases)
- [Changelog](https://github.com/PrismJS/prism/blob/master/CHANGELOG.md)
- [Commits](https://github.com/PrismJS/prism/compare/v1.24.0...v1.25.0)

---
updated-dependencies:
- dependency-name: prismjs
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-20 21:48:33 +00:00
39a5cd2d6e Bump set-value from 3.0.2 to 4.0.1 in /docs
Bumps [set-value](https://github.com/jonschlinkert/set-value) from 3.0.2 to 4.0.1.
- [Release notes](https://github.com/jonschlinkert/set-value/releases)
- [Commits](https://github.com/jonschlinkert/set-value/compare/3.0.2...4.0.1)

---
updated-dependencies:
- dependency-name: set-value
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-16 04:26:17 +00:00
784516283f Merge pull request #1399 from nikhen/i845
data.email should NOT be shorter than 8 characters #845
2021-09-13 11:03:22 +10:00
ce503232c3 data.email should NOT be shorter than 8 characters #845 2021-09-12 15:01:55 +02:00
f2edf9130f Merge pull request #1396 from nikhen/develop
1.1.1.1 used as a placeholder, despite being real external website/address #686
2021-09-12 20:42:51 +10:00
413ab50fc4 Change example IP: 0.0.0.0 -> 203.0.113.25 2021-09-12 12:28:25 +02:00
c1880bd3ff 1.1.1.1 used as a placeholder, despite being real external website/address #686 2021-09-11 17:21:32 +02:00
0f0a672275 Added another contributor 2021-09-10 14:49:11 +10:00
06c5f991e7 Merge pull request #1390 from jc21/develop
v2.9.9
2021-09-10 13:50:47 +10:00
babc5b7a38 Bumped version 2021-09-10 07:54:10 +10:00
b96c996a45 Log more info for internal errors in debug mode 2021-09-09 08:46:09 +10:00
fb8f2c2f9a Merge pull request #1384 from bergi9/patch-1
Add SSL and HTTP2 into IPv6 on listen.conf
2021-09-08 11:30:00 +10:00
6794937391 Merge pull request #1376 from realJoshByrnes/develop
Fixed some typos in Advanced Config readme
2021-09-08 10:52:25 +10:00
f022e84979 Add SSL and HTTP2 into IPv6 on listen.conf
I can only server contents with IPv6 because I'm sitting behind CGN on IPv4. When enabling HTTP2 it still not serve contents with HTTP2 as there are missing arguments in the `listen`. But it still does the SSL encryption.
Previous to this commit it generates:
```
listen 80;
listen [::]:80;

listen 443 ssl http2;
listen [::]:443;
```
Now it generates:
```
listen 80;
listen [::]:80;

listen 443 ssl http2;
listen [::]:443 ssl http2;
```
2021-09-07 22:50:49 +02:00
fd5ac952cc Fixed some typos in Advanced Config readme 2021-09-05 05:47:14 +10:00
07f60e5c77 Merge pull request #1367 from jc21/dependabot/npm_and_yarn/docs/tar-6.1.11
Bump tar from 6.1.6 to 6.1.11 in /docs
2021-09-02 11:52:52 +10:00
628b8a7e1f Merge pull request #1368 from jc21/dependabot/npm_and_yarn/backend/tar-4.4.19
Bump tar from 4.4.15 to 4.4.19 in /backend
2021-09-02 11:52:39 +10:00
30a442807d Bump tar from 4.4.15 to 4.4.19 in /backend
Bumps [tar](https://github.com/npm/node-tar) from 4.4.15 to 4.4.19.
- [Release notes](https://github.com/npm/node-tar/releases)
- [Changelog](https://github.com/npm/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/npm/node-tar/compare/v4.4.15...v4.4.19)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-02 00:11:00 +00:00
1626c8edd1 Merge pull request #1343 from ssrahul96/develop
Added support to download Let's Encrypt Certificate
2021-09-02 10:09:45 +10:00
ca6561bf6c updated debug statement 2021-09-01 11:50:51 +05:30
273a81471d Revert "updated debug statement"
This reverts commit 8b07a67133.
2021-09-01 11:47:47 +05:30
8b07a67133 updated debug statement 2021-09-01 11:46:10 +05:30
32089ea272 deferenced symlinks and downloaded the certs from live directory 2021-09-01 11:41:27 +05:30
658acd147c updated certificate path 2021-09-01 07:38:11 +05:30
ca3370a6ac Merge pull request #1366 from BjoernAkAManf/patch-1
Forwarding host should be anyOf not oneOf
2021-09-01 07:25:35 +10:00
c4e2557de2 Bump tar from 6.1.6 to 6.1.11 in /docs
Bumps [tar](https://github.com/npm/node-tar) from 6.1.6 to 6.1.11.
- [Release notes](https://github.com/npm/node-tar/releases)
- [Changelog](https://github.com/npm/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/npm/node-tar/compare/v6.1.6...v6.1.11)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-31 19:03:35 +00:00
6f2b4fdf86 Forwarding host should be anyOf not oneOf
Should fix #1354
2021-08-31 19:27:43 +02:00
f302ff71c9 corrected message 2021-08-30 16:36:13 +05:30
fee87a44d6 Merge pull request #1348 from jc21/develop
v2.9.8
2021-08-25 10:11:10 +10:00
8944609b63 fixed linting 2021-08-24 07:28:17 +05:30
be87c45f27 thrown exception for non LE certificates 2021-08-24 06:01:08 +05:30
1b1807c79a removed debug lines 2021-08-23 18:03:47 +05:30
a8f4699816 [frontend] certificate download changes 2021-08-23 15:47:03 +05:30
ac3df6dd77 fixed comments 2021-08-23 09:29:33 +05:30
5c67908460 Bump version, added contributors 2021-08-23 13:55:48 +10:00
7b67ef3015 fixed linting 2021-08-23 09:17:42 +05:30
e5a3b5ee2f added endpoint to download certificates 2021-08-23 09:03:24 +05:30
5e9ff4d2bf Add healthcheck back for ci containers 2021-08-23 09:29:11 +10:00
daa71764b6 Merge pull request #1338 from bmbvenom/patch-1
remove dummy cert references to Nginx Proxy Manager
2021-08-23 08:52:01 +10:00
6a6c2ef192 Remove healthchecks and mention how to optin to them in docs 2021-08-23 08:50:07 +10:00
320315956d remove dummy cert references to Nginx Proxy Manager
Based on this issue: https://github.com/jc21/nginx-proxy-manager/issues/1024
2021-08-21 22:37:14 -07:00
4f10d129c2 Reload nginx after access list change. Fixes #1328 2021-08-19 08:55:53 +10:00
62eb3fcd85 Updated docker base image location 2021-08-17 11:28:30 +10:00
ab40e4e2cf Merge pull request #1036 from BjoernAkAManf/master
Allows hostname instead of ip for streams
2021-08-16 13:40:40 +10:00
0bb9450642 Merge pull request #1323 from jc21/dependabot/npm_and_yarn/docs/url-parse-1.5.2
Bump url-parse from 1.5.0 to 1.5.2 in /docs
2021-08-16 13:38:16 +10:00
a6e15532b9 Bump url-parse from 1.5.0 to 1.5.2 in /docs
Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.5.0 to 1.5.2.
- [Release notes](https://github.com/unshiftio/url-parse/releases)
- [Commits](https://github.com/unshiftio/url-parse/compare/1.5.0...1.5.2)

---
updated-dependencies:
- dependency-name: url-parse
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-16 03:37:58 +00:00
9a89a8a77e Merge pull request #1209 from jc21/dependabot/npm_and_yarn/docs/prismjs-1.24.0
Bump prismjs from 1.23.0 to 1.24.0 in /docs
2021-08-16 13:37:56 +10:00
fe3675dc7a Merge pull request #1210 from jc21/dependabot/npm_and_yarn/backend/normalize-url-4.5.1
Bump normalize-url from 4.5.0 to 4.5.1 in /backend
2021-08-16 13:37:47 +10:00
5c9acc2bff Merge pull request #1309 from jc21/dependabot/npm_and_yarn/backend/path-parse-1.0.7
Bump path-parse from 1.0.6 to 1.0.7 in /backend
2021-08-16 13:37:36 +10:00
c94e937a50 Merge pull request #1308 from jc21/dependabot/npm_and_yarn/frontend/path-parse-1.0.7
Bump path-parse from 1.0.6 to 1.0.7 in /frontend
2021-08-16 13:37:26 +10:00
3e4e10e644 Merge pull request #1310 from jc21/dependabot/npm_and_yarn/docs/path-parse-1.0.7
Bump path-parse from 1.0.6 to 1.0.7 in /docs
2021-08-16 13:37:15 +10:00
ba7bb57ca2 Incorporate feedback
- Empty function removed
- Placeholder and Maxlength restored
- Validation improved
- Typo fixed
2021-08-13 11:32:01 +02:00
14c125150a Bump path-parse from 1.0.6 to 1.0.7 in /docs
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-12 03:03:43 +00:00
053701a702 Bump path-parse from 1.0.6 to 1.0.7 in /backend
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-12 00:57:11 +00:00
3fc3e43042 Bump path-parse from 1.0.6 to 1.0.7 in /frontend
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-12 00:56:50 +00:00
b0dc68d7d4 Merge pull request #1300 from FMeinicke/develop
Add deSEC DNS provider
2021-08-09 15:30:53 +10:00
e895baaeb4 Add deSEC DNS provider 2021-08-08 19:22:17 +02:00
c47f6fdb21 Merge pull request #1294 from jc21/develop
v2.9.7
2021-08-07 21:15:47 +10:00
9e188e441a Merge branch 'master' into develop 2021-08-07 20:06:40 +10:00
f6efcdf9f9 Bumped version 2021-08-07 20:05:53 +10:00
b1ceda3af4 Update letsencrypt.ini to support ECDSA keys
Since we have newer certbot available, it's time to support more modern and safer ECDSA keys instead of RSA.
2021-08-07 20:05:53 +10:00
cd3a0684d0 Merge pull request #1293 from jc21/dependabot/npm_and_yarn/docs/tar-6.1.6
Bump tar from 6.0.2 to 6.1.6 in /docs
2021-08-07 19:07:08 +10:00
f25e54c6cb Merge pull request #1211 from gabbe/dns-loopia
Added Loopia dns provider
2021-08-07 13:04:11 +10:00
66f86cf497 Merge pull request #1258 from nightah/fix-location-proxy_pass
Utilise variable for custom locations proxy_pass
2021-08-07 13:03:33 +10:00
d260edc547 Bump tar from 6.0.2 to 6.1.6 in /docs
Bumps [tar](https://github.com/npm/node-tar) from 6.0.2 to 6.1.6.
- [Release notes](https://github.com/npm/node-tar/releases)
- [Changelog](https://github.com/npm/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/npm/node-tar/compare/v6.0.2...v6.1.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-07 03:02:18 +00:00
ba1e6fa984 Merge pull request #1265 from phantomski77/master
Update letsencrypt.ini to support ECDSA keys
2021-08-07 13:01:14 +10:00
6b59f36213 Merge pull request #1287 from jc21/dependabot/npm_and_yarn/backend/tar-4.4.15
Bump tar from 4.4.13 to 4.4.15 in /backend
2021-08-07 13:00:55 +10:00
1894960762 Merge pull request #1286 from jc21/fixes-certificate-renewal
Fixes certificate renewal
2021-08-07 12:59:58 +10:00
83c5c55f32 Fixes creation of certificates using the http challenge 2021-08-06 10:56:06 +02:00
fb8c0b9a48 Bump tar from 4.4.13 to 4.4.15 in /backend
Bumps [tar](https://github.com/npm/node-tar) from 4.4.13 to 4.4.15.
- [Release notes](https://github.com/npm/node-tar/releases)
- [Changelog](https://github.com/npm/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/npm/node-tar/compare/v4.4.13...v4.4.15)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-04 20:15:26 +00:00
d34691152c Fixes renewal unused http certificates 2021-08-04 14:07:53 +02:00
cea80b482e Fixes certificate renewal for dns challenges 2021-08-04 13:47:44 +02:00
c460a8fa5c Update letsencrypt.ini to support ECDSA keys
Since we have newer certbot available, it's time to support more modern and safer ECDSA keys instead of RSA.
2021-07-28 11:25:24 +01:00
5f852437fe Merge pull request #1261 from jc21/develop
v2.9.6
2021-07-25 23:19:35 +10:00
8aded1a685 Bump version 2021-07-25 21:10:26 +10:00
f2acb9e150 Tweaks to s6 scripts 2021-07-25 21:09:02 +10:00
6f3a00c9b8 Merge pull request #1255 from jc21/fixes-crash-when-logrotation-fails 2021-07-24 08:50:12 +10:00
fbae107c04 Changes owner of logs to root on every container start 2021-07-23 09:11:43 +02:00
6c1ae77a2a Utilise variable for custom locations proxy_pass
If a custom location is currently set to proxy to a DNS hostname this hostname is cached by nginx. When the underlying IP for the hostname changes this will be cached in nginx until it is restarted. This behaviour is somewhat undesirable if utilising containers.

This change sets the proxy_pass for custom locations into a variable and utilises said variable for routing to the upstream backend. This will ensure that nginx will utilise the resolver and resolve the hostname to the current IP instead of relying on the nginx cache.
2021-07-23 16:24:46 +10:00
67e8ca6714 Fixes crash when logrotate fails 2021-07-22 14:05:21 +02:00
a56d976947 Merge pull request #1248 from jc21/develop
v2.9.5
2021-07-19 22:10:23 +10:00
ac0bb6bee2 Merge branch 'master' into develop 2021-07-19 15:59:06 +10:00
dee67dac75 Bumped version, added contributors 2021-07-19 15:58:15 +10:00
9458cfbd1a Merge pull request #1229 from demize/auth_request-fix
Disable auth_request in letsencrypt-acme-challenge.conf
2021-07-18 21:54:59 +10:00
4b8bdd22b3 Merge pull request #1181 from Theyooo/fix-wildcard-domains-regex
Fix wildcard domain regex
2021-07-18 19:05:23 +10:00
a4c143e2d1 Merge pull request #1178 from mrdink/patch-1
Update messages.json
2021-07-12 07:54:36 +10:00
e91019feb9 Merge pull request #1140 from jc21/adds-logrotation
Adds logrotation
2021-07-12 07:54:02 +10:00
8a37ec72b7 Merge pull request #1212 from jc21/dependabot/npm_and_yarn/docs/color-string-1.5.5
Bump color-string from 1.5.3 to 1.5.5 in /docs
2021-07-12 07:52:49 +10:00
c263a33095 Merge pull request #1222 from PUP-Loki/patch-1
Added Joker dns provider
2021-07-12 07:52:34 +10:00
4b2c0115db Add to letsencrypt-acme-challenge.conf to allow for ACME challenges on proxy hosts using auth_requests 2021-07-10 15:02:09 -04:00
673f40bd85 Removes force parameter from logrotate 2021-07-09 12:34:50 +02:00
b9f8108cd3 Added Joker dns provider 2021-07-07 08:49:30 +01:00
a16ecf656b Bump color-string from 1.5.3 to 1.5.5 in /docs
Bumps [color-string](https://github.com/Qix-/color-string) from 1.5.3 to 1.5.5.
- [Release notes](https://github.com/Qix-/color-string/releases)
- [Changelog](https://github.com/Qix-/color-string/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Qix-/color-string/commits/1.5.5)

---
updated-dependencies:
- dependency-name: color-string
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-30 23:58:23 +00:00
842cff130b Re-add missing certbot force renewal flag
and cleaned up this horrible file a bit
2021-07-01 09:57:26 +10:00
346b9b4b79 Added Loopia dns provider 2021-06-30 14:11:58 +02:00
56c317d223 All logs in single folder
nginx cannot create the folder structure for logs
2021-06-29 23:07:54 +02:00
b7b150a979 Run logrotation binary from program 2021-06-29 21:18:29 +02:00
fae848bd1b Store host logs in subfolders 2021-06-29 20:40:36 +02:00
a5b8087dc5 Bump normalize-url from 4.5.0 to 4.5.1 in /backend
Bumps [normalize-url](https://github.com/sindresorhus/normalize-url) from 4.5.0 to 4.5.1.
- [Release notes](https://github.com/sindresorhus/normalize-url/releases)
- [Commits](https://github.com/sindresorhus/normalize-url/commits)

---
updated-dependencies:
- dependency-name: normalize-url
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-29 11:59:33 +00:00
7aa078e025 Bump prismjs from 1.23.0 to 1.24.0 in /docs
Bumps [prismjs](https://github.com/PrismJS/prism) from 1.23.0 to 1.24.0.
- [Release notes](https://github.com/PrismJS/prism/releases)
- [Changelog](https://github.com/PrismJS/prism/blob/master/CHANGELOG.md)
- [Commits](https://github.com/PrismJS/prism/compare/v1.23.0...v1.24.0)

---
updated-dependencies:
- dependency-name: prismjs
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-28 19:45:20 +00:00
4b6b276b64 Merge pull request #1195 from jc21/develop
v2.9.4
2021-06-22 09:41:47 +10:00
0373daae5c Bump version 2021-06-22 08:55:37 +10:00
7f30dd0475 Merge pull request #1194 from jc21/dependabot/npm_and_yarn/docs/set-getter-0.1.1
Bump set-getter from 0.1.0 to 0.1.1 in /docs
2021-06-22 08:19:36 +10:00
d2a77c2371 Bump set-getter from 0.1.0 to 0.1.1 in /docs
Bumps [set-getter](https://github.com/doowb/set-getter) from 0.1.0 to 0.1.1.
- [Release notes](https://github.com/doowb/set-getter/releases)
- [Commits](https://github.com/doowb/set-getter/commits/0.1.1)

---
updated-dependencies:
- dependency-name: set-getter
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-21 21:26:26 +00:00
104f65b541 Merge pull request #1169 from jc21/dependabot/npm_and_yarn/test/glob-parent-5.1.2
Bump glob-parent from 5.1.0 to 5.1.2 in /test
2021-06-19 10:31:47 +10:00
d0fb4b6914 Merge pull request #1177 from jc21/dependabot/npm_and_yarn/frontend/postcss-7.0.36
Bump postcss from 7.0.32 to 7.0.36 in /frontend
2021-06-19 10:31:36 +10:00
42c3272def Merge pull request #1182 from DSorlov/develop
Update ssl-ciphers.conf
2021-06-19 10:30:43 +10:00
2812889d61 Merge pull request #1186 from jc21/use-certbot-from-path
Use the certbot command from the PATH variable
2021-06-19 10:24:21 +10:00
bd3a13b2a5 Also rotate other logs 2021-06-18 10:43:56 +02:00
289d179142 Adds logrotate 2021-06-18 09:38:48 +02:00
deca493912 Splits access and error logs for each host 2021-06-18 09:38:48 +02:00
d16bf7d6c0 Adds explicit names to dev containers 2021-06-18 09:38:48 +02:00
3f1415dad1 Use the certbot command from the PATH variable 2021-06-18 09:22:42 +02:00
3e744b6b2d Update ssl-ciphers.conf
Removing support (by default) for all the unsecure protocols. This should be the default and if needed additional support can be configured. As this is a security feature it should be aligned with a moderate policy. This is updated using the latest recomendation as found on https://ssl-config.mozilla.org/#server=nginx&version=1.17.7&config=intermediate&openssl=1.1.1d&guideline=5.6
2021-06-17 15:17:13 +02:00
56c4f8a106 fix wildcard domains regex 2021-06-17 11:51:51 +02:00
99ef8bae4c Update messages.json
small change but updated the year in the footer. maybe there's a better way to grab the date with a function?
2021-06-16 09:29:55 -04:00
b7f0343730 Bump postcss from 7.0.32 to 7.0.36 in /frontend
Bumps [postcss](https://github.com/postcss/postcss) from 7.0.32 to 7.0.36.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/7.0.32...7.0.36)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-16 10:16:48 +00:00
c807b59fb4 Merge pull request #1149 from Fuechslein/develop 2021-06-16 20:16:08 +10:00
60fc57431a Merge pull request #1136 from jc21/dependabot/npm_and_yarn/docs/ws-7.4.6
Bump ws from 7.3.1 to 7.4.6 in /docs
2021-06-11 12:11:30 +10:00
d988a3a307 Bump glob-parent from 5.1.0 to 5.1.2 in /test
Bumps [glob-parent](https://github.com/gulpjs/glob-parent) from 5.1.0 to 5.1.2.
- [Release notes](https://github.com/gulpjs/glob-parent/releases)
- [Changelog](https://github.com/gulpjs/glob-parent/blob/main/CHANGELOG.md)
- [Commits](https://github.com/gulpjs/glob-parent/compare/v5.1.0...v5.1.2)

---
updated-dependencies:
- dependency-name: glob-parent
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-11 01:26:02 +00:00
de763d3fa9 Merge pull request #1170 from jc21/dependabot/npm_and_yarn/docs/glob-parent-5.1.2
Bump glob-parent from 5.1.1 to 5.1.2 in /docs
2021-06-11 11:25:28 +10:00
21bfb61cc8 Merge pull request #1068 from jc21/dependabot/npm_and_yarn/frontend/underscore-1.12.1
Bump underscore from 1.10.2 to 1.12.1 in /frontend
2021-06-11 10:01:30 +10:00
a79adeb280 Bump ws from 7.3.1 to 7.4.6 in /docs
Bumps [ws](https://github.com/websockets/ws) from 7.3.1 to 7.4.6.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.3.1...7.4.6)

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-10 23:08:37 +00:00
9b7a019222 Merge pull request #1159 from jc21/changes-real_ip-ranges-to-local-network
Sets real_ip ranges to local network only
2021-06-11 09:07:57 +10:00
e65143d276 Merge pull request #1164 from jc21/dependabot/npm_and_yarn/docs/normalize-url-5.3.1
Bump normalize-url from 5.1.0 to 5.3.1 in /docs
2021-06-11 09:07:06 +10:00
61bb183eda Bump glob-parent from 5.1.1 to 5.1.2 in /docs
Bumps [glob-parent](https://github.com/gulpjs/glob-parent) from 5.1.1 to 5.1.2.
- [Release notes](https://github.com/gulpjs/glob-parent/releases)
- [Changelog](https://github.com/gulpjs/glob-parent/blob/main/CHANGELOG.md)
- [Commits](https://github.com/gulpjs/glob-parent/compare/v5.1.1...v5.1.2)

---
updated-dependencies:
- dependency-name: glob-parent
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-10 23:06:36 +00:00
f3d3a6db91 Merge pull request #1165 from jc21/dependabot/npm_and_yarn/docs/css-what-5.0.1
Bump css-what from 3.3.0 to 5.0.1 in /docs
2021-06-11 09:05:46 +10:00
9ebb443cb7 Bump css-what from 3.3.0 to 5.0.1 in /docs
Bumps [css-what](https://github.com/fb55/css-what) from 3.3.0 to 5.0.1.
- [Release notes](https://github.com/fb55/css-what/releases)
- [Commits](https://github.com/fb55/css-what/compare/v3.3.0...v5.0.1)

---
updated-dependencies:
- dependency-name: css-what
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-09 19:18:07 +00:00
fa4c4d0a8d Bump normalize-url from 5.1.0 to 5.3.1 in /docs
Bumps [normalize-url](https://github.com/sindresorhus/normalize-url) from 5.1.0 to 5.3.1.
- [Release notes](https://github.com/sindresorhus/normalize-url/releases)
- [Commits](https://github.com/sindresorhus/normalize-url/commits)

---
updated-dependencies:
- dependency-name: normalize-url
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-09 00:57:18 +00:00
3e1518fef6 Updated docs for docker restart policy 2021-06-08 08:48:26 +10:00
e59db84721 Merge pull request #1155 from ChrisTracy/patch-1
Adding "restart: always" to ReadMe
2021-06-08 08:45:28 +10:00
c281b31fc8 Update README.md 2021-06-07 09:38:45 -05:00
1c93ca9896 Merge pull request #1158 from jc21/fixes-duckdns-certbot 2021-06-07 18:33:25 +10:00
df5836e573 Sets real_ip ranges to local network only 2021-06-07 08:30:39 +02:00
41ef35f0d0 Fixes duckdns certbot 2021-06-07 08:27:01 +02:00
5e6ce8643f Adds reddit badge to readme 2021-06-07 06:19:29 +00:00
f575400bc8 Merge pull request #1081 from vipergts450/vipergts450-custom_location-patch
Vipergts450 custom location patch
2021-06-07 12:46:23 +10:00
6d9a335b0e Merge pull request #1129 from jc21/dependabot/npm_and_yarn/docs/dns-packet-5.2.2
Bump dns-packet from 5.2.1 to 5.2.2 in /docs
2021-06-07 12:44:26 +10:00
f94eb5f318 Merge pull request #1123 from jc21/dependabot/npm_and_yarn/docs/browserslist-4.16.5
Bump browserslist from 4.13.0 to 4.16.5 in /docs
2021-06-07 09:30:49 +10:00
245fa421a2 Adding "restart: always" to ReadMe
Adding "restart: always" to ReadMe. I had a hard time figuring out why once of my NPM containers would not start on reboot while the rest of them would.
2021-06-06 12:14:58 -05:00
655094a816 Update certbot-dns-plugins.js 2021-06-06 16:44:44 +02:00
d544650b05 fix spacings. again. 2021-06-03 19:42:14 +02:00
d9d682585c fix spaces/tabs 2021-06-03 19:35:30 +02:00
44a202552e Restore default build script 2021-06-03 18:36:25 +02:00
fd6673420b Added IONOS and Infomaniak certbot plugins 2021-06-03 18:33:33 +02:00
0e9cd5db9c Merge pull request #1122 from jc21/fixes-some-database-issues
Fixes some database issues on raspberry pi
2021-05-31 08:43:28 +10:00
6da6e6f145 Bump dns-packet from 5.2.1 to 5.2.2 in /docs
Bumps [dns-packet](https://github.com/mafintosh/dns-packet) from 5.2.1 to 5.2.2.
- [Release notes](https://github.com/mafintosh/dns-packet/releases)
- [Changelog](https://github.com/mafintosh/dns-packet/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mafintosh/dns-packet/compare/v5.2.1...v5.2.2)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-26 18:51:46 +00:00
bdaa7460e4 Bump browserslist from 4.13.0 to 4.16.5 in /docs
Bumps [browserslist](https://github.com/browserslist/browserslist) from 4.13.0 to 4.16.5.
- [Release notes](https://github.com/browserslist/browserslist/releases)
- [Changelog](https://github.com/browserslist/browserslist/blob/main/CHANGELOG.md)
- [Commits](https://github.com/browserslist/browserslist/compare/4.13.0...4.16.5)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-26 04:04:42 +00:00
d6d1c1ac35 Updates raspberry pi docker compose in documentation 2021-05-25 14:18:51 +02:00
67958155fc Updates generated sqlite config in documentation 2021-05-25 14:18:01 +02:00
198bd74ec6 Adds useNullAsDefault to default sqlite config 2021-05-25 14:16:46 +02:00
92eec95dad Merge pull request #1111 from jc21/develop
v2.9.3
2021-05-21 08:59:01 +10:00
b73a034fca Merge branch 'master' into develop 2021-05-20 13:34:43 +10:00
11b508f021 Bump version, added contributors 2021-05-20 13:31:14 +10:00
c2b5be37f9 Merge pull request #966 from razvanstoica89/master
Add new details about how to run this image on a Raspberry Pi device.
2021-05-20 08:15:31 +10:00
1a04863a08 Merge pull request #972 from psharma04/master
Add info about SSL
2021-05-20 08:14:28 +10:00
077d3eb993 Merge pull request #1071 from jc21/dependabot/npm_and_yarn/frontend/ua-parser-js-0.7.28
Bump ua-parser-js from 0.7.21 to 0.7.28 in /frontend
2021-05-20 08:12:41 +10:00
ac38221bdf Merge pull request #1094 from jc21/dependabot/npm_and_yarn/docs/postcss-8.2.10
Bump postcss from 7.0.32 to 8.2.10 in /docs
2021-05-20 08:12:25 +10:00
9f146b8fc3 Merge pull request #1099 from jc21/updates-dns-providers
Updates Porkbun, DuckDNS & Azure challenge providers
2021-05-20 08:12:12 +10:00
9a2d9659a7 Merge pull request #1100 from jc21/show-entity-ids-in-frontend
Displays entity id for each entry in frontend
2021-05-20 08:11:12 +10:00
6faae5350a Merge pull request #1107 from jc21/improved-issue-templates
Updates issue templates
2021-05-20 08:10:01 +10:00
2616709e7f Reactivates Azure dns challenge with new version 2021-05-19 21:07:56 +02:00
bed9ff084c Adds dns challenge issue template 2021-05-18 20:44:06 +02:00
fb3082b0bb Updates issue templates to prevent segment information to show up in issue 2021-05-18 20:10:50 +02:00
c8439b8429 Displays entity id for each entry in frontend
Displays this id inside the three-dot-menu on the right
2021-05-16 16:57:05 +02:00
4c3632d3e7 Automatically sort DNS challenge providers by display name on render 2021-05-15 19:16:28 +02:00
24d8cd57da Updates DuckDNS to a working version 2021-05-15 18:35:49 +02:00
b7c0a8b0c2 Sorts providers alphabetically 2021-05-15 18:32:49 +02:00
d6d7b22b1e Adds porkbun as dns provider 2021-05-15 18:31:33 +02:00
5269c957ce Merge pull request #1085 from jc21/improved-new-password-error-messages
Improved new password error messages
2021-05-13 08:24:12 +10:00
bf8d2672c8 Typo (used \m instead of \n) 2021-05-13 08:24:08 +10:00
8461acab9b Merge pull request #1088 from bikram990/develop
Added support for Dynu DNS Provider
2021-05-13 08:23:10 +10:00
731ae43fe8 Merge pull request #1095 from jc21/godaddy-dns
Adds GoDaddy DNS and temporarily disables Azure DNS
2021-05-13 08:22:37 +10:00
5354c85352 Merge pull request #1096 from jc21/serve-fonts-locally
Serves font locally
2021-05-13 08:22:00 +10:00
22a37bb923 Disables azure dns provider temporarily 2021-05-12 18:49:28 +02:00
07bbece704 Serves font locally 2021-05-12 18:03:10 +02:00
9af2f50d81 Adds GoDaddy DNS provider 2021-05-12 16:28:09 +02:00
7b148f7970 Bump postcss from 7.0.32 to 8.2.10 in /docs
Bumps [postcss](https://github.com/postcss/postcss) from 7.0.32 to 8.2.10.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/7.0.32...8.2.10)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-12 06:59:49 +00:00
a1e52d919f Added support for Dynu DNS Provider 2021-05-11 20:24:52 +05:30
899b487daa Puts backend errors into own error field 2021-05-10 19:58:28 +02:00
86eeb5fc44 Merge pull request #1084 from jc21/develop
v2.9.2
2021-05-10 09:10:13 +10:00
62a708b416 Version bump 2021-05-10 08:18:19 +10:00
a7ce8704b3 Merge pull request #1069 from jc21/dependabot/npm_and_yarn/backend/lodash-4.17.21
Bump lodash from 4.17.19 to 4.17.21 in /backend
2021-05-10 08:15:22 +10:00
7319a13077 Merge pull request #1074 from jc21/dependabot/npm_and_yarn/frontend/lodash-4.17.21
Bump lodash from 4.17.19 to 4.17.21 in /frontend
2021-05-10 08:15:13 +10:00
95bd4d93c5 Merge pull request #1080 from jc21/dependabot/npm_and_yarn/frontend/hosted-git-info-2.8.9
Bump hosted-git-info from 2.8.8 to 2.8.9 in /frontend
2021-05-10 08:15:01 +10:00
69c33f0395 Merge pull request #1083 from chaptergy/adds-more-dns-challenges
Adds more dns challenges
2021-05-10 08:14:07 +10:00
cd4caea2dc Adds coudns dns provider 2021-05-09 21:47:30 +02:00
c9daf19940 Reorders some providers alphabetically 2021-05-09 21:36:52 +02:00
7c2540b193 Adds Azure dns provider
Issue #864
2021-05-09 19:46:20 +02:00
3e600552dc Adds regru dns provider
Issue #938
2021-05-09 19:45:42 +02:00
69ee6b1699 Update nginx.js 2021-05-07 23:38:32 -04:00
ca1ea042b2 Update nginx.js 2021-05-07 23:31:51 -04:00
f26df7d9bb Update nginx.js 2021-05-07 22:43:22 -04:00
ba45705571 Partial revert of 421934e
Keeping the server block of websocket definitions but also bringing back the
location block after discussions on #1067
2021-05-08 12:17:10 +10:00
bf8ea71c77 Bump hosted-git-info from 2.8.8 to 2.8.9 in /frontend
Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-08 02:12:03 +00:00
7deb64a5de Bump lodash from 4.17.19 to 4.17.21 in /backend
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.19 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.19...4.17.21)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-08 02:11:35 +00:00
e283865d3d Bump lodash from 4.17.19 to 4.17.21 in /frontend
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.19 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.19...4.17.21)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-08 02:11:13 +00:00
a32be3e96b Merge pull request #1070 from jc21/dependabot/npm_and_yarn/docs/lodash-4.17.21
Bump lodash from 4.17.19 to 4.17.21 in /docs
2021-05-08 12:10:48 +10:00
0cfd6eab3f Merge pull request #1072 from jc21/dependabot/npm_and_yarn/docs/url-parse-1.5.0
Bump url-parse from 1.4.7 to 1.5.0 in /docs
2021-05-08 12:10:40 +10:00
c2361f13e6 Merge pull request #1073 from jc21/dependabot/npm_and_yarn/test/lodash-4.17.21
Bump lodash from 4.17.19 to 4.17.21 in /test
2021-05-08 12:10:30 +10:00
bc81de54b9 Merge pull request #1078 from jc21/master 2021-05-08 00:21:17 +10:00
07884bc9b1 Merge pull request #1076 from jc21/develop
v2.9.1
2021-05-07 18:57:53 +10:00
58c3204187 Bump lodash from 4.17.19 to 4.17.21 in /test
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.19 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.19...4.17.21)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-07 03:52:24 +00:00
19d3deddd4 Bump url-parse from 1.4.7 to 1.5.0 in /docs
Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.4.7 to 1.5.0.
- [Release notes](https://github.com/unshiftio/url-parse/releases)
- [Commits](https://github.com/unshiftio/url-parse/compare/1.4.7...1.5.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-07 03:51:57 +00:00
f0233b947e Bump ua-parser-js from 0.7.21 to 0.7.28 in /frontend
Bumps [ua-parser-js](https://github.com/faisalman/ua-parser-js) from 0.7.21 to 0.7.28.
- [Release notes](https://github.com/faisalman/ua-parser-js/releases)
- [Commits](https://github.com/faisalman/ua-parser-js/compare/0.7.21...0.7.28)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-07 03:51:51 +00:00
c6a90a2fd0 Bump lodash from 4.17.19 to 4.17.21 in /docs
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.19 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.19...4.17.21)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-07 03:51:51 +00:00
3607c30d98 Bump version 2021-05-07 13:51:35 +10:00
717105f243 Revert installing certbot. This is handled by base image jc21/nginx-full now
Update path of certbot, and use the pip instead
2021-05-07 13:49:31 +10:00
2373e4a06d Bump underscore from 1.10.2 to 1.12.1 in /frontend
Bumps [underscore](https://github.com/jashkenas/underscore) from 1.10.2 to 1.12.1.
- [Release notes](https://github.com/jashkenas/underscore/releases)
- [Commits](https://github.com/jashkenas/underscore/compare/1.10.2...1.12.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-07 03:05:13 +00:00
4c76803f13 Rearrange _location.conf template
Allow more of the main host parameters into the custom location configuration and reorder to make more sense.
2021-05-06 22:30:45 -04:00
6a46e88f8f Fix renderLocations to accept more parameters from host
Amend renderLocations to pass more host parameters into the custom locations to match the requirements set for the default location. This will apply all parameters set in the UI to all custom locations.
2021-05-06 22:29:21 -04:00
e4e94d5be0 Update nginx.js
Adds ability for custom location templates to handle includes
2021-05-06 14:50:09 -04:00
a3b896fa70 Update _location.conf 2021-05-06 14:48:38 -04:00
60347a90e9 Update _location.conf 2021-05-06 11:40:40 -04:00
a02d4ec46f Use certbot from pip instead of apt 2021-05-06 19:10:40 +10:00
485bae8f22 Merge pull request #1064 from jc21/develop
v2.9.0
2021-05-06 12:45:21 +10:00
655477316b Version bump, contributors added, apt tweak 2021-05-06 11:32:54 +10:00
e22f87dee7 Merge pull request #975 from thegamingninja/feature/more-access-ips
Improved Access List Form
2021-05-06 11:06:08 +10:00
d3337322dd Add target _blank to href 2021-05-05 21:02:04 -04:00
6202f4f943 Merge pull request #1034 from jonasled/patch-2
don't fix the select height, to fix multiline select
2021-05-06 10:54:12 +10:00
b42cc9ed3e Merge pull request #1050 from jc21/dependabot/npm_and_yarn/frontend/y18n-4.0.3
Bump y18n from 4.0.0 to 4.0.3 in /frontend
2021-04-30 07:16:06 +10:00
fbf72c0f61 Merge pull request #958 from italobb/patch-1
docker-compose Instructions for streams support
2021-04-29 21:49:13 +10:00
cbd0b0c070 Merge pull request #964 from jc21/dependabot/npm_and_yarn/docs/is-svg-4.2.2
Bump is-svg from 4.2.1 to 4.2.2 in /docs
2021-04-29 21:45:00 +10:00
874f049323 Merge pull request #965 from jc21/dependabot/npm_and_yarn/docs/ssri-8.0.1
Bump ssri from 8.0.0 to 8.0.1 in /docs
2021-04-29 21:44:39 +10:00
42ab4020e2 Bump y18n from 4.0.0 to 4.0.3 in /frontend
Bumps [y18n](https://github.com/yargs/y18n) from 4.0.0 to 4.0.3.
- [Release notes](https://github.com/yargs/y18n/releases)
- [Changelog](https://github.com/yargs/y18n/blob/y18n-v4.0.3/CHANGELOG.md)
- [Commits](https://github.com/yargs/y18n/compare/v4.0.0...y18n-v4.0.3)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-29 11:44:15 +00:00
7ab9683b87 Merge pull request #987 from jc21/dependabot/npm_and_yarn/backend/y18n-4.0.1
Bump y18n from 4.0.0 to 4.0.1 in /backend
2021-04-29 21:43:59 +10:00
865facfd05 Merge pull request #988 from jc21/dependabot/npm_and_yarn/test/y18n-4.0.1
Bump y18n from 4.0.0 to 4.0.1 in /test
2021-04-29 21:43:37 +10:00
0951f4a202 Merge pull request #989 from jc21/dependabot/npm_and_yarn/docs/y18n-4.0.1
Bump y18n from 4.0.0 to 4.0.1 in /docs
2021-04-29 21:43:22 +10:00
882ec27969 Merge pull request #1017 from Stealthii/bugfix/config-application
Apply 'Force SSL' and 'Enable Websockets' to full host definition
2021-04-29 21:42:58 +10:00
a84158c1ff Merge pull request #1020 from stegmannb/patch-1
Updated credentials for netcup dns challenge
2021-04-29 21:40:49 +10:00
161d3ec817 Bump ssri from 8.0.0 to 8.0.1 in /docs
Bumps [ssri](https://github.com/npm/ssri) from 8.0.0 to 8.0.1.
- [Release notes](https://github.com/npm/ssri/releases)
- [Changelog](https://github.com/npm/ssri/blob/latest/CHANGELOG.md)
- [Commits](https://github.com/npm/ssri/compare/v8.0.0...v8.0.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-29 11:38:19 +00:00
5b15249689 Merge pull request #1042 from hepelayo/duckdnsCertbotPlugin
Add DuckDNS certbot plugin
2021-04-29 21:37:58 +10:00
602fce1c7e Merge pull request #1046 from jc21/dependabot/npm_and_yarn/frontend/ssri-6.0.2
Bump ssri from 6.0.1 to 6.0.2 in /frontend
2021-04-29 21:36:55 +10:00
f2f653e345 Remove platform specific determination 2021-04-29 11:19:59 +10:00
b55738bd6e Bump ssri from 6.0.1 to 6.0.2 in /frontend
Bumps [ssri](https://github.com/npm/ssri) from 6.0.1 to 6.0.2.
- [Release notes](https://github.com/npm/ssri/releases)
- [Changelog](https://github.com/npm/ssri/blob/v6.0.2/CHANGELOG.md)
- [Commits](https://github.com/npm/ssri/compare/v6.0.1...v6.0.2)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-28 23:39:48 +00:00
b39a59ce72 Merge pull request #1045 from jc21/nginx-full
Switch to nginx-full base
2021-04-29 09:29:46 +10:00
9872daf29f Switch to nginx-full base 2021-04-29 08:28:40 +10:00
91044e730b Correct indentation 2021-04-26 22:35:52 +02:00
656a7dceef Correct spacing 2021-04-26 22:31:16 +02:00
d636502eaa Add DuckDNS certbot plugin
Uses https://github.com/infinityofspace/certbot_dns_duckdns certbot plugin to implement DuckDNS DNS challenge in order to obtain Wildcard certificates.

Requires that the user inserts his duckDNS token in "Credentials File Content" input box.
2021-04-26 20:25:24 +02:00
389fd158ad allows hostname instead of ip for streams 2021-04-24 01:09:01 +02:00
30fa63b379 don't fix the select height, to fix multiline select 2021-04-23 21:25:57 +02:00
691063545c Updated credentials for netcup dns challange
The netcup DNS challenge currently fails because the credentials are not found.
2021-04-15 09:48:08 +02:00
421934efed Move 'Allow Websockets' definitions to host root configuration
This fixes issues with these settings not applying to custom locations
defined under hosts.
2021-04-13 20:04:35 +01:00
f056b9dc7f Move 'Force SSL' definitions to host root configuration
This fixes issues with these settings not applying to custom locations
defined under hosts.
2021-04-13 19:59:49 +01:00
48d421ba28 Bump y18n from 4.0.0 to 4.0.1 in /docs
Bumps [y18n](https://github.com/yargs/y18n) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/yargs/y18n/releases)
- [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yargs/y18n/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-31 17:28:00 +00:00
096b714117 Bump y18n from 4.0.0 to 4.0.1 in /test
Bumps [y18n](https://github.com/yargs/y18n) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/yargs/y18n/releases)
- [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yargs/y18n/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-31 14:28:11 +00:00
0d25dc1aaa Bump y18n from 4.0.0 to 4.0.1 in /backend
Bumps [y18n](https://github.com/yargs/y18n) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/yargs/y18n/releases)
- [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yargs/y18n/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-31 14:25:26 +00:00
63d3c2d06f Add More Access List Entries & Access List Help 2021-03-25 14:59:07 -04:00
5fc704ccad Add info about SSL 2021-03-23 13:49:14 +11:00
61d99561c1 Set ports to default value 2021-03-22 09:50:25 +02:00
c26ce2083f Add new details about how to run this image on a Raspberry Pi device. 2021-03-21 18:02:53 +02:00
847d71f72a Bump is-svg from 4.2.1 to 4.2.2 in /docs
Bumps [is-svg](https://github.com/sindresorhus/is-svg) from 4.2.1 to 4.2.2.
- [Release notes](https://github.com/sindresorhus/is-svg/releases)
- [Commits](https://github.com/sindresorhus/is-svg/compare/v4.2.1...v4.2.2)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-21 00:12:10 +00:00
3c35039445 docker-compose Instructions for streams support
A simple example to make the docker-compose configuration support stream configurations.
2021-03-18 01:20:47 -03:00
1a64d44857 Merge pull request #955 from jc21/develop
v2.8.1
2021-03-18 09:36:26 +10:00
ba5f0c212c Bumped version 2021-03-17 14:56:22 +10:00
4eddb5d7f3 Merge pull request #769 from jc21/dependabot/npm_and_yarn/frontend/ini-1.3.8
Bump ini from 1.3.5 to 1.3.8 in /frontend
2021-03-17 14:54:45 +10:00
3b104710d5 Restrict redirect http code 2021-03-17 14:53:57 +10:00
74db0004bd Merge pull request #883 from baruffaldi/master
Forward scheme and http code added for redirection hosts
2021-03-17 11:30:26 +10:00
6e67352a0f Merge pull request #880 from jlesage/jwt-key-pair-no-restart
Removed the need to restart Nginx Proxy Manager after generating JWT key pair
2021-03-17 11:09:37 +10:00
b127f02468 Added upgrade steps 2021-03-17 08:58:45 +10:00
c9c53d9670 Merge pull request #903 from ogarai/edit-disabled-host
Prevent activating nginx config after editing disabled proxy host
2021-03-17 08:32:14 +10:00
d36dbb868b Merge pull request #942 from nielscil/develop-certbot-dns-transip
Added TransIP dns provider
2021-03-17 08:30:05 +10:00
b7fb2cfe92 Merge pull request #947 from phantomski77/patch-2
Update _hsts.conf template to increase HSTS max-age value
2021-03-17 08:29:46 +10:00
d0a0c77556 acmedns indentation fix 2021-03-16 13:36:17 +01:00
9469b9c78a fixed closing brace 2021-03-16 12:57:26 +01:00
e4988f34c7 Merge pull request #943 from phantomski77/patch-1
Update README.md
2021-03-16 19:51:03 +10:00
1fe9e24f0a Merge pull request #920 from jc21/dependabot/npm_and_yarn/docs/prismjs-1.23.0
Bump prismjs from 1.21.0 to 1.23.0 in /docs
2021-03-16 19:50:28 +10:00
9c39de3454 Merge pull request #935 from jc21/dependabot/npm_and_yarn/frontend/elliptic-6.5.4
Bump elliptic from 6.5.3 to 6.5.4 in /frontend
2021-03-16 19:50:14 +10:00
9bb68ad4eb Merge pull request #937 from jc21/dependabot/npm_and_yarn/docs/elliptic-6.5.4
Bump elliptic from 6.5.3 to 6.5.4 in /docs
2021-03-16 19:49:59 +10:00
5bf774bee1 Merge branch 'develop' into develop-certbot-dns-transip 2021-03-16 19:49:27 +10:00
99514464fb Merge pull request #946 from ijaron/dns-acme
Add ACME-DNS certbot plugin
2021-03-16 19:48:13 +10:00
3bf1c1e531 Merge pull request #948 from GurjinderSingh/patch-1
Update README.md
2021-03-16 13:59:04 +10:00
e2e31094aa Fix CI, no var needed for this 2021-03-16 11:32:38 +10:00
f29ff15167 Update README.md
found it in config file was going to add option
	# Custom
	include /data/nginx/custom/http_top[.]conf;
 
b7b808d98d/docker/rootfs/etc/nginx/nginx.conf (L72)
2021-03-14 05:13:26 -04:00
1c64252015 Update _hsts.conf template
I propose the change to max-age value of HSTS from 1 year to 2 years.
2021-03-13 12:40:47 +00:00
ead19915f3 Add ACME-DNS certbot plugin
uses https://github.com/pan-net-security/certbot-dns-acmedns in to implement ACME-DNS support.
Requires file in /data with following layout
# cat /data/acme-registration.json
{
  "something.acme.com": {
    "username": "6e14735c-2c6a-447e-b63d-a23ac4438bd7",
    "password": "dd6gnYS-IxrQfDLbdPRX3hrFhS_SLrwbS0kSl_i8",
    "fulldomain": "3b750a0e-c627-423f-9966-4799c6a9533b.auth.example.org",
    "subdomain": "3b750a0e-c627-423f-9966-4799c6a9533b",
    "allowfrom": []
  }
}
2021-03-12 14:24:56 -06:00
967e0dd98a Update README.md
Secrets need both global and local per-service declaration, otherwise fails as "not found". Local configuration added for both app and db.
2021-03-11 21:59:26 +00:00
bd0324dba0 credentials property naming for transip
credentials property naming for transip
2021-03-11 15:44:20 +01:00
607fb83a1e Added transIP certbot dns plugin 2021-03-11 13:33:42 +01:00
bb5fc58f3a Revert "Fix CI, use docker for a yarn command"
This reverts commit afbec0aca9.
2021-03-11 09:29:08 +10:00
afbec0aca9 Fix CI, use docker for a yarn command 2021-03-11 08:41:10 +10:00
1e5d9dfbff Bump elliptic from 6.5.3 to 6.5.4 in /docs
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.3 to 6.5.4.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.3...v6.5.4)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-10 10:39:22 +00:00
cfcb657cde Bump elliptic from 6.5.3 to 6.5.4 in /frontend
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.3 to 6.5.4.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.3...v6.5.4)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-10 08:19:41 +00:00
7f243e6f06 Bump prismjs from 1.21.0 to 1.23.0 in /docs
Bumps [prismjs](https://github.com/PrismJS/prism) from 1.21.0 to 1.23.0.
- [Release notes](https://github.com/PrismJS/prism/releases)
- [Changelog](https://github.com/PrismJS/prism/blob/master/CHANGELOG.md)
- [Commits](https://github.com/PrismJS/prism/compare/v1.21.0...v1.23.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-01 21:06:50 +00:00
7e7032c051 Prevent activating nginx config after editing disabled proxy host 2021-02-18 23:05:30 +00:00
b7b808d98d Merge pull request #899 from jc21/develop
Docs for a docker network
2021-02-17 20:56:10 +10:00
a21289bf11 Docs for a docker network 2021-02-17 20:55:12 +10:00
4a8d01224a Merge pull request #897 from jc21/develop
Update github templates and docs
2021-02-17 20:19:49 +10:00
f0eebc43e3 Update github templates and docs 2021-02-17 20:18:12 +10:00
49fbf031d1 $scheme is displayed as "auto" on hosts listing 2021-02-10 19:43:29 +01:00
4060718e5c Hotfix: This group of assignments is not aligned 2021-02-10 18:26:19 +01:00
49b0f11ae7 migrations 2021-02-10 18:13:12 +01:00
9b83d35ef4 Migrations 2021-02-10 18:12:02 +01:00
eb20add0c7 Migrations hotfix 2021-02-10 17:16:33 +01:00
1f122e9145 Database migrations created 2021-02-10 15:57:28 +01:00
329d0ecaed Added forward scheme to table columns 2021-02-10 15:27:11 +01:00
77a2ee948a Updated list table
Added state code columns
Better form columns sizing
2021-02-10 15:25:19 +01:00
ebeda6345e Globalization done
Ready to merge
2021-02-10 14:11:01 +01:00
e35138ebed Added scheme to definitions 2021-02-10 10:42:34 +01:00
8ba6c4f7e7 Added http_code definition 2021-02-10 09:55:41 +01:00
6df7b72e08 Forward scheme and http code added for redirection hosts
You can now configure the forward_scheme and forward_http_code on user interface (section redirection hosts)
2021-02-09 11:23:15 +01:00
fe13b12f43 Removed the need to restart Nginx Proxy Manager after generating JWT key pair. 2021-02-08 17:36:58 -05:00
ea28da90b2 Merge pull request #876 from jc21/develop
v2.8.0
2021-02-08 15:01:31 +10:00
b243324c65 Added contributors and bumped version 2021-02-08 12:23:52 +10:00
a2dde00f40 Merge pull request #868 from lorisbergeron/eurodns-as-provider
Add EuroDNS as a DNS provider
2021-02-08 12:22:33 +10:00
5ff07faa7e Merge pull request #872 from ahgraber/master
Add Docker secrets
2021-02-08 11:59:23 +10:00
272c652847 Updated docs to use latest mariadb-aria image 2021-02-08 11:56:21 +10:00
3964bbf3fe update advanced-config/readme with secrets 2021-02-07 19:12:20 -05:00
11175aaa5f revert docker/dev before PR 2021-02-07 09:52:37 -05:00
7fcc4a7ef0 cleanup 2021-02-06 20:05:40 -05:00
5abb9458c7 fix linebreaks in secrets 2021-02-05 23:47:30 -05:00
0ca5587a6f EuroDNS fix key-spacing error 2021-02-05 23:40:31 +01:00
d29650882b EuroDNS fix key-spacing error 2021-02-05 23:29:01 +01:00
9c3a7b02ec Add EuroDNS as a DNS provider 2021-02-05 23:17:51 +01:00
ef3a073af5 local builds & secrets 2021-02-05 16:52:24 -05:00
15c4857a4b fix /docker/dev/docker-compose.yaml 2021-02-04 14:03:17 -05:00
63a71afbc8 beta s6 secrets 2021-02-04 11:25:26 -05:00
64761ee9c6 beta secrets 2021-02-04 11:15:31 -05:00
d6c344b5ec add local docker-compose to build 2021-02-02 08:58:45 -05:00
d27826d10e Merge pull request #850 from MooBaloo/master
Add new custom http.conf above includes for NPM-generated files
2021-02-01 12:10:31 +10:00
4ac52a0e25 Add custom .conf above includes for NPM-generated files.
Added a new clause for custom http_top.conf above the include clauses for NPM-generated files. Allows for more flexibility with adding custom nginx .conf files to NPM

Use case: adding a configuration change needs to be present before other custom configuration files are called and reference configuration from the custom http_top.conf file.
Example: add a new log_format in http_top.conf, then referencing it in a access_log clause in server_proxy.conf.
2021-01-28 05:52:41 -05:00
efa841d75a Merge pull request #842 from Shuro/patch-1
Use configured default page also for IPv6
2021-01-28 08:45:12 +10:00
d1fac583ea Use configured default page also for IPv6
Just a small check for the ipv6 variable, similar to _listen.conf,
so that the configured default page is also delivered on ipv6 requests.
2021-01-25 01:28:50 +01:00
8cb44c7b97 Merge pull request #830 from jc21/develop
Updated version fixes #823
2021-01-18 12:15:20 +10:00
f2293a9dda Merge branch 'master' into develop 2021-01-18 12:15:04 +10:00
da0d1d4a2f Updated version fixes #823 2021-01-18 12:14:13 +10:00
6a8d5e2166 Merge pull request #820 from jc21/develop
v2.7.3
2021-01-12 11:13:35 +10:00
d732665a23 Merge branch 'master' into develop 2021-01-12 09:06:34 +10:00
e0748c9bc7 Bumped version 2021-01-12 09:05:27 +10:00
23573543a3 Bump ini from 1.3.5 to 1.3.8 in /frontend
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.8.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.8)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-06 23:36:33 +00:00
bfb328238e Merge pull request #806 from jc21/develop
v2.7.2
2021-01-07 09:35:35 +10:00
64cc4f57d6 Version bump and acknowledgements 2021-01-06 21:48:10 +10:00
7a3c91c6a4 Merge pull request #804 from lebrou34/master
Add Gandi Live DNS to certbot-dns-plugins.js
2021-01-06 13:14:54 +10:00
508bc62852 Update certbot-dns-plugins.js 2021-01-05 18:47:22 +01:00
59e8446d47 Update certbot-dns-plugins.js 2021-01-05 18:41:26 +01:00
d13596d2f7 Update certbot-dns-plugins.js 2021-01-05 18:35:18 +01:00
9adccfa341 Update certbot-dns-plugins.js 2021-01-05 18:19:27 +01:00
5cc3b53378 Update certbot-dns-plugins.js 2021-01-05 18:11:10 +01:00
b62b0a2fb7 Update certbot-dns-plugins.js 2021-01-05 17:36:47 +01:00
1faac4edf2 Merge pull request #750 from klutchell/klutchell-patch-1
allow custom stream conf
2021-01-03 20:14:55 +10:00
4c60dce169 Merge pull request #796 from lightglitch/patch-1
Fix dead hosts verification count
2021-01-03 20:14:36 +10:00
771f31f44d Merge pull request #770 from jc21/dependabot/npm_and_yarn/backend/ini-1.3.8
Bump ini from 1.3.5 to 1.3.8 in /backend
2021-01-03 20:14:21 +10:00
8bedb95e1d Merge pull request #771 from jc21/dependabot/npm_and_yarn/test/ini-1.3.8
Bump ini from 1.3.5 to 1.3.8 in /test
2021-01-03 20:14:03 +10:00
ac4be08df2 Merge pull request #772 from jc21/dependabot/npm_and_yarn/docs/ini-1.3.8
Bump ini from 1.3.5 to 1.3.8 in /docs
2021-01-03 20:13:49 +10:00
0d6e058e23 Merge pull request #774 from chaptergy/better-custom-certificate-handling
Better custom certificate handling
2021-01-03 12:09:55 +10:00
bee2ceb667 Fix dead hosts verification count 2021-01-02 04:58:14 +00:00
6af13d4f40 Removes explicit privkeytype check and adds passphrase error 2020-12-14 12:08:39 +01:00
9dd0ebd899 Bump ini from 1.3.5 to 1.3.8 in /docs
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.8.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.8)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-12 10:17:11 +00:00
6e97bfa717 Bump ini from 1.3.5 to 1.3.8 in /test
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.8.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.8)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-12 08:23:59 +00:00
07b69f41eb Bump ini from 1.3.5 to 1.3.8 in /backend
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.8.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.8)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-12 08:23:45 +00:00
6bd2ac7d6d Update README.md 2020-12-01 14:24:14 -05:00
528e5ef3bc allow custom stream conf
Allow a top-level custom `stream` configuration file to be loaded.
2020-12-01 14:22:31 -05:00
bc1c50ac92 Added contributor 2020-11-22 21:50:57 +10:00
8c2ab42b75 Merge pull request #738 from WaterCalm/master
add aliyun DNS plugin
2020-11-22 21:48:24 +10:00
62053d15d4 add aliyun DNS plugin
add aliyun DNS plugin
2020-11-22 16:08:56 +08:00
6fed642aba Cypress docker build should be faster and added mkcert for later 2020-11-22 16:57:12 +10:00
72ac549a58 Merge pull request #729 from jc21/develop
v2.7.1
2020-11-18 22:55:47 +10:00
9f38617135 Merge branch 'master' into develop 2020-11-18 22:55:37 +10:00
94eec805df Version bump 2020-11-18 21:46:21 +10:00
05a940e732 Fix instances where config file exists and env vars don't 2020-11-18 21:42:03 +10:00
1c43cc2181 Merge pull request #725 from jc21/develop
v2.7.0
2020-11-18 14:30:44 +10:00
657ee73ff1 Merge branch 'master' into develop 2020-11-18 12:22:34 +10:00
4ee5d993cf Bumped version 2020-11-18 12:21:35 +10:00
70a445e2d7 Merge pull request #704 from chaptergy/allow-setup-without-config-file
Removes the need of a config file and allows db config via environment
2020-11-18 12:18:45 +10:00
2115da210d Merge pull request #694 from chaptergy/visual-indicator-for-certificate-deletion
Adds visual indicator for certificate deletion
2020-11-09 10:10:55 +10:00
540554c4f6 Merge pull request #695 from chaptergy/failed-certificate-autoremove
Adds autoremove of failed certificate creations in DB
2020-11-09 10:10:00 +10:00
1337c50d28 Use latest tag in full setup instructions 2020-11-07 19:37:35 +01:00
c5ceb3b2b1 Removes obsolete file mount 2020-11-07 13:54:18 +01:00
57fc1d8f08 Removes the need of a config file and allows db config via environment 2020-11-07 13:24:01 +01:00
1518ecd1e9 Adds autoremove of failed certificate creations in DB 2020-11-06 12:29:38 +01:00
6be0343918 Adds visual indicator for certificate deletion 2020-11-06 11:51:42 +01:00
cf8812c932 Merge pull request #692 from jc21/develop
v2.6.2
2020-11-06 19:31:10 +10:00
5bc3e474a9 Merge branch 'real_ip' of github.com:jc21/nginx-proxy-manager into develop 2020-11-06 13:21:37 +10:00
13eaa346bc Use remote addr as real ip 2020-11-06 13:21:22 +10:00
d7437cc4a7 Test for real-ip header 2020-11-06 13:17:30 +10:00
ddb3c6590c Version bump 2020-11-06 13:06:15 +10:00
89d6773bda Merge branch 'develop' of github.com:jc21/nginx-proxy-manager into real_ip 2020-11-06 09:18:25 +10:00
3651b9484f Fix for pip install error when there are no plugins to install 2020-11-06 09:17:52 +10:00
2200c950b7 Merge branch 'develop' of github.com:jc21/nginx-proxy-manager into real_ip 2020-11-06 09:12:35 +10:00
14f84f01b5 Merge pull request #687 from chaptergy/allow-additional-dns-challenge-dependencies
Allow additional dns challenge dependencies
2020-11-06 09:02:35 +10:00
cb014027bb Makes sure credentials folder exist every time before saving credentials 2020-11-04 19:31:40 +01:00
32e5155783 Fixes Linting errors 2020-11-03 22:38:09 +01:00
a3159ad59e Converts tabs to spaces 2020-11-03 22:24:03 +01:00
60a40197f1 Always install additional dependencies for dns plugins 2020-11-03 21:59:18 +01:00
7d693a4271 Expands and refactors dns plugin list 2020-11-03 21:28:50 +01:00
f192748bf9 Use x-real-ip header for the real-ip module 2020-10-19 11:40:50 +10:00
96f401cba6 Merge pull request #664 from chaptergy/fixes-expiring-hosts-renewal
Adds certbot plugin installation check on startup
2020-10-19 08:50:44 +10:00
ffd2430160 Merge pull request #666 from MarceloLagos/master
Check key for RSA header otherwise use EC, and output fix.
2020-10-19 08:45:28 +10:00
190cd2d6bb Update certificate.js 2020-10-17 23:46:18 -06:00
7ba58bdbd3 Update certificate.js 2020-10-17 23:27:12 -06:00
08ab62108f Fixes eslint errors 2020-10-17 12:54:38 +02:00
1028de8158 Adds certbot plugin installation check on startup 2020-10-17 12:13:08 +02:00
301499dc52 Merge pull request #659 from jc21/develop
v2.6.1
2020-10-16 15:53:56 +10:00
5c2f13ed8e Merge branch 'master' into develop 2020-10-16 13:44:10 +10:00
e30ad81f69 Updated version 2020-10-16 13:43:13 +10:00
21f36f535f Don't spit out a ; if the preceeding value is empty 2020-10-16 13:41:08 +10:00
c14236823a Merge pull request #656 from chaptergy/fixes-custom-certificate-upload
Fixes custom certificate upload
2020-10-16 08:33:51 +10:00
551a9fe1c6 Fixes custom certificate upload 2020-10-15 14:58:05 +02:00
e3399e1035 Merge pull request #654 from jc21/develop
2.6.0 Release
2020-10-15 15:14:57 +10:00
c413b4af3f Added contributors 2020-10-15 14:06:21 +10:00
dbf5dec23b Bump version 2020-10-15 10:40:01 +10:00
10f0eb17d7 Fix linting errors 2020-10-15 10:33:51 +10:00
e3b680c351 Merge pull request #653 from jmwebslave/dont-pass-auth-header
Pass/Don't Pass Auth Header
2020-10-15 10:10:33 +10:00
0df0545777 Allows auth information from AccessList not to be passed to proxied hosts. Resolves issue #153.
Signed-off-by: James Morgan <jmorgan.au+github@gmail.com>
2020-10-15 10:23:09 +11:00
165bfc9f5f Merge pull request #607 from jc21/dependabot/npm_and_yarn/docs/node-forge-0.10.0
Bump node-forge from 0.9.1 to 0.10.0 in /docs
2020-10-15 08:34:14 +10:00
5830bd73b9 Merge pull request #608 from Philip-Mooney/master
Fix for access list getAll when not granted all permissions
2020-10-15 08:33:58 +10:00
3c4ce839b9 Merge pull request #635 from chaptergy/allow-more-dns-challenges
Allow DNS challenges not just for cloudflare
2020-10-14 19:12:15 +10:00
ac9f052309 Fixes linting errors 2020-10-14 09:55:45 +02:00
049e424957 Adds special case for Route53 2020-10-14 09:20:52 +02:00
07e78aec48 Adds error stack information in prod environment for certificates 2020-10-08 15:30:13 +02:00
3fec135fe5 Fixes ESlint formatting errors 2020-10-08 14:38:19 +02:00
867fe1322b Unifies directory structure in dev and prod containers 2020-10-08 13:38:20 +02:00
95208a50a7 Increases timeouts in front- and backend 2020-10-08 13:21:17 +02:00
514b13fcc2 Fixes build issues due to globally used file 2020-10-06 16:12:12 +02:00
4cbc1f5bbe Minor refactoring 2020-10-06 15:37:51 +02:00
64de36cdf2 Adds more DNS plugins 2020-10-06 15:16:45 +02:00
093b48ad7b Implements backend changes to allow more dns challenges 2020-10-06 14:52:06 +02:00
05f6a55a0b Adds frontend improvements and fixes 2020-10-06 14:49:02 +02:00
2523424f68 Updates dockerfiles 2020-10-05 01:04:18 +02:00
b81325d7bf Implements dns challenge provider selection in frontend 2020-10-05 01:04:06 +02:00
3e10b7b2b1 Fix for access list getAll when not granted all permissions 2020-09-19 22:16:16 +01:00
e5cb750015 Bump node-forge from 0.9.1 to 0.10.0 in /docs
Bumps [node-forge](https://github.com/digitalbazaar/forge) from 0.9.1 to 0.10.0.
- [Release notes](https://github.com/digitalbazaar/forge/releases)
- [Changelog](https://github.com/digitalbazaar/forge/blob/master/CHANGELOG.md)
- [Commits](https://github.com/digitalbazaar/forge/compare/0.9.1...0.10.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-17 23:42:28 +00:00
28f72086ec Merge pull request #592 from jc21/develop
v2.5.0
2020-09-04 09:07:47 +10:00
a6b9bd7b01 Version bump and contributors 2020-09-03 14:11:44 +10:00
2c5eac9dad Merge branch 'master' of github.com:jc21/nginx-proxy-manager into develop 2020-09-03 14:03:43 +10:00
87f61b8527 Merge pull request #572 from jipjan/features/dns-cloudflare
Add DNS CloudFlare with wildcard support
2020-09-03 14:01:05 +10:00
74bfe490c6 Merge pull request #587 from duhruh/bug/custom-ssl-inputs
Allow inputs to update
2020-09-03 13:53:17 +10:00
015167f34d Allow inputs to update 2020-08-29 20:24:51 -07:00
4bafc7ff1a Merge pull request #546 from jc21/dependabot/npm_and_yarn/docs/prismjs-1.21.0
Bump prismjs from 1.20.0 to 1.21.0 in /docs
2020-08-25 10:51:11 +10:00
bf8beb50b4 Merge pull request #559 from jlesage/remove-webroot-certbot-arg
Removed the hardcoded `--webroot` certbot argument to better support DNS challenge
2020-08-25 08:44:00 +10:00
e5034a34f5 Merge pull request #570 from jc21/dependabot/npm_and_yarn/backend/bcrypt-5.0.0
Bump bcrypt from 4.0.1 to 5.0.0 in /backend
2020-08-25 08:31:48 +10:00
a561605653 show in ssl certificates list that CloudFlare is used 2020-08-24 09:09:52 +00:00
e8596c1554 cloudflare DNS also possible while adding proxy, redirection and 404 2020-08-24 09:00:00 +00:00
ab67481e99 fix eslint errors 2020-08-23 18:56:25 +00:00
1b611e67c8 Merge commit 'c5aa2b9f771cbd4c78c239ed0791aeb8d9e4d2e4' into features/dns-cloudflare 2020-08-23 18:30:07 +00:00
c5aa2b9f77 add cloudflare renew and make revoke working for both by deleting unnecessary config command 2020-08-23 18:29:16 +00:00
cff6c4d1f5 - prevent wildcard generation when not using Cloudflare dns
- fix cloudflare token required logic
2020-08-23 16:48:14 +00:00
077cf75ef2 wildcard support 2020-08-23 13:24:20 +00:00
ff1770204c request via cloudflare dns working 2020-08-23 12:50:41 +00:00
b9a95840e0 add cloudflare dns option to letsencrypt via manual certificate 2020-08-23 11:40:41 +00:00
2d7576c57e add cloudflare dns also to dev docker file 2020-08-23 10:54:36 +00:00
251aac716a Add CloudFlare DNS plugin to certbot 2020-08-21 09:49:43 +02:00
6694a42270 Merge pull request #560 from jlesage/remove-from-unixtime
Removed usage of `FROM_UNIXTIME` mysql-specific function.
2020-08-21 14:21:40 +10:00
f78a4c6ad1 Bump bcrypt from 4.0.1 to 5.0.0 in /backend
Bumps [bcrypt](https://github.com/kelektiv/node.bcrypt.js) from 4.0.1 to 5.0.0.
- [Release notes](https://github.com/kelektiv/node.bcrypt.js/releases)
- [Changelog](https://github.com/kelektiv/node.bcrypt.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kelektiv/node.bcrypt.js/compare/v4.0.1...v5.0.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-08-20 17:01:00 +00:00
83fad8bcda Removed usage of FROM_UNIXTIME mysql-specific function.
This provide better interoperability with different databases (e.g. sqlite).
Fixes #557
2020-08-14 19:31:05 -04:00
f539e813aa Removed the hardcoded --webroot certbot argument to better support DNS challenge. Also, this option is already set in the default letsencrypt.ini. 2020-08-14 14:28:03 -04:00
5d65166777 Ignore local subnets for real IP determination 2020-08-12 09:32:40 +10:00
70346138a7 Bump prismjs from 1.20.0 to 1.21.0 in /docs
Bumps [prismjs](https://github.com/PrismJS/prism) from 1.20.0 to 1.21.0.
- [Release notes](https://github.com/PrismJS/prism/releases)
- [Changelog](https://github.com/PrismJS/prism/blob/master/CHANGELOG.md)
- [Commits](https://github.com/PrismJS/prism/compare/v1.20.0...v1.21.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-08-08 00:02:04 +00:00
d68656559c Merge pull request #544 from jlesage/sqlite-now-helper-fix
Fixed now_helper for sqlite (time is missing)
2020-08-07 08:37:00 +10:00
01660b5b80 Fixed now_helper for sqlite: it should also returns the time. 2020-08-06 17:16:22 -04:00
74010acd85 Merge pull request #543 from jc21/develop
v2.4.0
2020-08-06 16:00:10 +10:00
7c7d255172 Added another contributor 2020-08-06 14:46:19 +10:00
058f1e9835 Merge pull request #464 from vrenjith/patch-1
Update location-item.ejs - forward_host size increase to 200
2020-08-06 14:45:09 +10:00
b4fc629ec0 Bumped version 2020-08-06 14:43:34 +10:00
ae06b2da75 Updated deps and added contributor 2020-08-06 14:40:54 +10:00
54d423a11f Updated doc for sqlite 2020-08-06 14:27:29 +10:00
5da6c97a00 Pull cypress tests from correct location 2020-08-06 13:57:33 +10:00
bf2f13443f Cypress fixes 2020-08-06 12:47:24 +10:00
9ce4c3fe2f CI fix 2020-08-06 12:02:47 +10:00
4a07bf666d Added users cypress tests 2020-08-06 11:57:31 +10:00
5be46b4b20 Cypress fixes 2020-08-06 11:26:37 +10:00
7fd825b76b Use development config file in CI 2020-08-06 10:59:25 +10:00
b23d59dec7 Updated cypress to 4.12.1 2020-08-06 09:00:52 +10:00
492d450d26 Sqlite Tweaks
- Added cypress testing in CI for sqlite
- Cleaned up promises in setup
- Ensure check for settings is strict
2020-08-06 08:58:20 +10:00
04412f3624 Merge pull request #510 from tg44/multidb-re
Multidb - sqlite support
2020-08-06 08:33:00 +10:00
c41057b28a Revert builx push experiment 2020-07-31 09:28:45 +10:00
8312bc0100 Use same tags for experiment 2020-07-30 14:00:59 +10:00
85ac43bc5e Merge branch 'master' of github.com:jc21/nginx-proxy-manager into develop 2020-07-30 08:31:18 +10:00
d1a0780c7a Attempt to circumvent docker login token timeouts 2020-07-30 08:30:26 +10:00
f9b8d76527 Merge pull request #513 from jc21/dependabot/npm_and_yarn/frontend/lodash-4.17.19
Bump lodash from 4.17.15 to 4.17.19 in /frontend
2020-07-20 12:39:10 +10:00
26f00eeae4 Merge branch 'master' into dependabot/npm_and_yarn/frontend/lodash-4.17.19 2020-07-20 10:59:15 +10:00
1bc2df2178 Merge pull request #514 from jc21/dependabot/npm_and_yarn/docs/lodash-4.17.19
Bump lodash from 4.17.15 to 4.17.19 in /docs
2020-07-20 10:58:36 +10:00
8dfbcef198 Bump lodash from 4.17.15 to 4.17.19 in /docs
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-19 20:33:49 +00:00
6690b7735d sqlite3 and psql support 2020-07-19 20:04:29 +02:00
a9e7222e5e introduced now_helper for multidb capabilities 2020-07-19 20:03:53 +02:00
f8edeb2775 fixed migration and setup
more info: https://github.com/knex/knex/issues/2820
2020-07-19 20:02:20 +02:00
d1786fe159 Bump lodash from 4.17.15 to 4.17.19 in /frontend
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-19 15:20:28 +00:00
157a12fb7c Update location-item.ejs 2020-06-19 01:56:16 +05:30
3f723b1638 Merge pull request #454 from jc21/develop
v2.3.1
2020-06-09 09:47:31 +10:00
e2e9835d01 Version bump 2020-06-09 09:17:25 +10:00
7599617b67 Merge pull request #452 from jc21/dependabot/npm_and_yarn/docs/websocket-extensions-0.1.4
Bump websocket-extensions from 0.1.3 to 0.1.4 in /docs
2020-06-08 11:14:20 +10:00
18a5b11033 Bump websocket-extensions from 0.1.3 to 0.1.4 in /docs
Bumps [websocket-extensions](https://github.com/faye/websocket-extensions-node) from 0.1.3 to 0.1.4.
- [Release notes](https://github.com/faye/websocket-extensions-node/releases)
- [Changelog](https://github.com/faye/websocket-extensions-node/blob/master/CHANGELOG.md)
- [Commits](https://github.com/faye/websocket-extensions-node/compare/0.1.3...0.1.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-06-07 23:36:47 +00:00
fff31b0f34 Ensure we're using the latest node image 2020-06-03 10:30:29 +10:00
c02e30663a Revert last 2020-06-02 20:21:27 +10:00
4c6527cafc Ensure python2 is installed for frontend build 2020-06-02 20:09:27 +10:00
55bddb12e5 Merge pull request #435 from Subv/access_lists_ips
Don't use duplicate relations when eager-loading access list items and clients.
2020-06-02 19:42:27 +10:00
d95861e1fb Don't use duplicate relations when eager-loading access list items and clients.
This fixes an Objection warning: 'Duplicate relation "proxy_hosts" in a relation expression. You should use "a.[b, c]" instead of "[a.b, a.c]". This will cause an error in objection 2.0'.

It also fixes the access list clients not being properly eager-loaded when building the proxy host nginx configuration files. Closes #434
2020-05-29 20:29:34 -05:00
94754a5cb3 Revert CI debug 2020-05-28 20:26:16 +10:00
546f862236 Merge pull request #429 from jc21/develop
v2.3.0
2020-05-28 17:06:19 +10:00
f105e29e56 Merge branch 'master' into develop 2020-05-28 15:38:38 +10:00
5c15993d06 Contributors 7 wide 2020-05-28 13:31:41 +10:00
a369ea1080 Bump version 2020-05-28 13:29:55 +10:00
98068c0f57 Debug CI by leaving images alive 2020-05-28 13:26:36 +10:00
e0ef8683a2 Merge pull request #428 from jc21/openresty
Openresty base
2020-05-28 12:22:31 +10:00
66412a75f9 Revert to node base now that base has openresty 2020-05-28 09:25:29 +10:00
84d8fb0899 Merge pull request #403 from Indemnity83/empty-auth
Don't ask for username/password if none are defined
2020-05-28 09:18:50 +10:00
c631537dbe Don't wipe out nginx dir, keeps luajit 2020-05-27 10:38:00 +10:00
8d2f49541c Use OpenResty base image 2020-05-26 14:38:41 +10:00
55a28e3437 Merge branch 'develop' of github.com:jc21/nginx-proxy-manager into develop 2020-05-25 14:53:46 +10:00
67ea2d01c8 Added gitter, contributors 2020-05-25 14:53:35 +10:00
dab229e37c Merge pull request #406 from theraw/patch-1
set proper timeout.
2020-05-25 14:37:06 +10:00
7084473330 Merge pull request #416 from jc21/develop
v2.2.4
2020-05-21 16:52:16 +10:00
dd2e335fae Cypress 4.6.0 and tweaks to scripts 2020-05-21 16:11:19 +10:00
1ff87bbc12 Version bump 2020-05-21 15:09:51 +10:00
2ebfdcf0c9 Fix LE certs for IPv6 only domains Fixes 394 2020-05-20 22:21:26 +10:00
8ab161a3ee Merge branch 'develop' of github.com:jc21/nginx-proxy-manager into develop 2020-05-20 21:53:44 +10:00
e74b9617be Added product support github template 2020-05-20 21:40:54 +10:00
c3d88c83e3 Merge pull request #402 from Indemnity83/patch-2
Fix address validation rule to allow 'all' keyword
2020-05-20 21:16:11 +10:00
3e912a7474 Added FAQ to docs 2020-05-20 21:14:00 +10:00
0d726a1d83 Merge pull request #405 from Indemnity83/fix-satisfy
fix spelling of 'satisfy'
2020-05-20 20:44:38 +10:00
affabf065e set proper timeout. 2020-05-11 00:24:02 +02:00
e6ea77d263 fix spelling of 'satisfy'
Fixes #385
2020-05-09 18:01:43 -07:00
df73c2a458 skip auth check if no users defined 2020-05-09 15:51:11 -07:00
96c5c79aef Fix address validation rule to allow 'all' keyword
The rule was looking for the keyword 'any' but should have been looking for 'all' 

http://nginx.org/en/docs/http/ngx_http_access_module.html
2020-05-09 09:31:58 -07:00
64922f07ff Merge pull request #388 from jc21/dependabot/npm_and_yarn/frontend/jquery-3.5.0
Bump jquery from 3.4.1 to 3.5.0 in /frontend
2020-05-07 14:53:24 +10:00
bae21f3210 Merge pull request #397 from Indemnity83/patch-1
apply migration to correct table
2020-05-05 10:14:47 +10:00
0702a4e58e Fix incorrect var 2020-05-05 10:00:41 +10:00
31f1d304d6 apply migration to correct table 2020-05-04 16:56:26 -07:00
291a74c295 Bump jquery from 3.4.1 to 3.5.0 in /frontend
Bumps [jquery](https://github.com/jquery/jquery) from 3.4.1 to 3.5.0.
- [Release notes](https://github.com/jquery/jquery/releases)
- [Commits](https://github.com/jquery/jquery/compare/3.4.1...3.5.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-04-30 14:43:28 +00:00
c0e9d1eb2f Fix satisy typo 2020-04-22 11:11:20 +10:00
a7cabdde3a Merge pull request #376 from spalger/expand-forward-host-size
expand the maximum size of the forward_host
2020-04-17 08:59:55 +10:00
3af560c2d0 switch to 255 limit to match db 2020-04-16 15:14:49 -07:00
1d23d5c761 remove maxlength from html too 2020-04-16 15:13:28 -07:00
995db12f22 remove arbitrary length limit of forward_host 2020-04-16 14:00:22 -07:00
4c60bfb66b Merge pull request #370 from jc21/develop
v2.2.3
2020-04-15 15:06:56 +10:00
1716747047 Merge branch 'master' into develop 2020-04-15 14:19:07 +10:00
090b4d0388 Version bump 2020-04-15 14:18:27 +10:00
a9f068daa8 Merge pull request #360 from Indemnity83/ip-access-control
Client Access Lists
2020-04-15 08:29:40 +10:00
f5ee91aeb3 write access list to proxy host config 2020-04-13 23:32:00 -07:00
e2ee2cbf2d enforce a 'deny all' default rule
this ensures that an access list is 'secure by default' and requires the user to create exceptions or holes in the proection instead of building the wall entirely. This also means that we no longer require the user to input any username/passwords or client addressses and can avoid internal errors which generate unhelpful user errors.
2020-04-13 23:31:54 -07:00
dcf8364899 Merge pull request #368 from jc21/develop
Support ipv6 address as a origin header, hopefully fixes #149
2020-04-14 14:40:00 +10:00
b783602786 Support ipv6 address as a origin header, hopefully fixes #149 2020-04-14 13:01:13 +10:00
005e64eb9f valite auth/access rules in backend 2020-04-13 19:23:55 -07:00
e9e5d293cc expand address format
now accepts CIDR notation, IPv6 or the string 'any'
2020-04-13 19:16:18 -07:00
a57255350f Merge pull request #365 from jc21/develop
Develop
2020-04-14 09:10:45 +10:00
781442bf1e Merge pull request #361 from Xantios/fix-bad-gateway
Fixes #310 Clarification on the docs
2020-04-14 09:09:39 +10:00
604bd2c576 Merge pull request #358 from dpanesso/dev-formatting
Documentation formatting
2020-04-14 08:37:23 +10:00
d9e1e1bbb7 Fixes #310 Clarification on the docs 2020-04-11 13:03:15 +02:00
907e9e182d remove testing cruft 2020-04-11 00:42:58 -07:00
0f238a5021 add satisfy configuration to the ui 2020-04-11 00:26:54 -07:00
8d432bd60a refine the UI labeling 2020-04-10 20:22:01 -07:00
fd932c7678 fix bugs preventing client rules from being updated 2020-04-10 17:42:44 -07:00
46a9f5cb96 add basic functionality to front end 2020-04-10 17:33:14 -07:00
f990d3f674 add access list clients to back-end 2020-04-10 16:38:54 -07:00
4a6de8deee Documentation formatting on advanced configuration page 2020-04-10 00:57:45 -05:00
9a7a216b23 Merge pull request #352 from jc21/develop
Develop
2020-04-07 12:09:17 +10:00
fccaaaae4d Merge branch 'master' into develop 2020-04-07 12:09:09 +10:00
a882b0be82 Merge branch 'develop' of github.com:jc21/nginx-proxy-manager into develop 2020-04-07 12:06:55 +10:00
db7bbab768 Updated npm deps 2020-04-07 12:06:36 +10:00
030e553549 Merge pull request #351 from jc21/develop
v2.2.2 Release
2020-04-07 12:01:48 +10:00
8b0ca8e367 Merge branch 'master' into develop 2020-04-07 11:23:03 +10:00
83b2b07200 Version bump 2020-04-07 10:45:45 +10:00
bdb591af9e - Add ability to disable ipv6, fixes #312
- Added ipv6 listening to hosts when configured, fixes #236 and #149
- Added documentation about disabling ipv6
- Updated npm packages
2020-04-07 10:43:19 +10:00
2993a08777 Merge pull request #349 from jc21/develop
v2.2.1
2020-04-06 10:23:45 +10:00
2a2d3d57ec Version bump 2020-04-06 09:13:40 +10:00
33c2c131c2 Merge pull request #345 from IronTooch/master
Documentation update
2020-04-06 08:33:02 +10:00
e4286c96a7 Merge pull request #342 from damianog/patch-1
Update proxy_host.conf
2020-04-06 08:32:48 +10:00
2d9486b6fd Merge pull request #347 from jc21/dependabot/npm_and_yarn/test/acorn-7.1.1
Bump acorn from 7.1.0 to 7.1.1 in /test
2020-04-06 07:55:40 +10:00
632ee2d0bd Merge pull request #348 from jc21/dependabot/npm_and_yarn/backend/acorn-7.1.1
Bump acorn from 7.1.0 to 7.1.1 in /backend
2020-04-06 07:36:16 +10:00
b09f201819 Bump acorn from 7.1.0 to 7.1.1 in /backend
Bumps [acorn](https://github.com/acornjs/acorn) from 7.1.0 to 7.1.1.
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/7.1.0...7.1.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-04-04 12:29:15 +00:00
baaf39c23d Bump acorn from 7.1.0 to 7.1.1 in /test
Bumps [acorn](https://github.com/acornjs/acorn) from 7.1.0 to 7.1.1.
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/7.1.0...7.1.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-04-04 12:28:57 +00:00
b7467c10e8 Update README.md 2020-04-01 23:44:24 -04:00
701ef18606 Update README.md 2020-04-01 23:42:58 -04:00
3e7d2b216b Update proxy_host.conf
FIX as explained on #340
2020-03-27 19:01:21 +01:00
41f16c20b6 Bump minimist from 1.2.0 to 1.2.2 in /docs
Bumps [minimist](https://github.com/substack/minimist) from 1.2.0 to 1.2.2.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.0...1.2.2)

Signed-off-by: dependabot[bot] <support@github.com>
2020-03-19 09:53:16 +10:00
96bc0b53c3 Manually applying #296 - nickname required in ui 2020-03-12 08:46:18 +10:00
b80baa78ef Updated readme (#323)
* Tweaks and backend vscode settings

* Version bump

* Updated Icon to be more vibrant

* New logo and new login screen layout, version bump

* New documentation!

* Use CI to update package versions

because I was sick of changing it everywhere

* Generate docs

* Docs upload

* Fix pipeline

* Fix pipeline

* Update readme version before generating docs

* Testing docs deploy

* Fix pipeline

* Updated CI link

* Fix docs upload

* Docs upload fixes

* Fix s3 upload grrr

* Docs tweaks

* Updated readme

* Updated screenshots

* Updated cdn id for docs

* Updated github image

* Nicer readme header

Co-authored-by: Jamie Curnow <jamiec@benon.com>
2020-03-12 08:43:50 +10:00
ce88e0745d Updated cdn id for docs (#322)
* Tweaks and backend vscode settings

* Version bump

* Updated Icon to be more vibrant

* New logo and new login screen layout, version bump

* New documentation!

* Use CI to update package versions

because I was sick of changing it everywhere

* Generate docs

* Docs upload

* Fix pipeline

* Fix pipeline

* Update readme version before generating docs

* Testing docs deploy

* Fix pipeline

* Updated CI link

* Fix docs upload

* Docs upload fixes

* Fix s3 upload grrr

* Docs tweaks

* Updated readme

* Updated screenshots

* Updated cdn id for docs

Co-authored-by: Jamie Curnow <jamiec@benon.com>
2020-03-12 08:36:01 +10:00
256bd2336f v2.2.0 New Brand (#319)
* Tweaks and backend vscode settings

* Version bump

* Updated Icon to be more vibrant

* New logo and new login screen layout, version bump

* New documentation!

* Use CI to update package versions

because I was sick of changing it everywhere

* Generate docs

* Docs upload

* Fix pipeline

* Fix pipeline

* Update readme version before generating docs

* Testing docs deploy

* Fix pipeline

* Updated CI link

* Fix docs upload

* Docs upload fixes

* Fix s3 upload grrr

* Docs tweaks

* Updated readme

* Updated screenshots

Co-authored-by: Jamie Curnow <jamiec@benon.com>
2020-03-11 16:54:10 +10:00
1b6993ee70 Added better Build Badge 2020-03-10 09:59:34 +10:00
af319b4729 Updated doc and example to use jc21/mariadb-aria now that it supports multiple arch 2020-03-09 15:22:24 +10:00
1a15b4f18d Also archive db ci log 2020-03-06 13:21:13 +10:00
3ddd3b49fd Re-order post testing steps 2020-03-06 13:11:04 +10:00
e2c4b32311 Merge branch 'master' of github.com:jc21/nginx-proxy-manager 2020-03-06 12:54:15 +10:00
8fb2821343 Get artifacts on build fail 2020-03-06 12:54:05 +10:00
93f29734b7 Version 2.1.2 (#317)
* Tweaks and backend vscode settings

* Version bump
2020-03-06 11:04:42 +10:00
569d2b0dce Merge branch 'develop' of github.com:jc21/nginx-proxy-manager 2020-02-21 12:05:49 +10:00
b7e1e4fd9e Use the corresponding s6 binary for the built arch - fixes #298 2020-02-21 10:52:43 +10:00
a1cebb889c Use cypress 4.0.2 2020-02-20 12:58:47 +10:00
f040de5788 Update backend packages
Liquid required some changes
Version bump
2020-02-20 09:17:45 +10:00
e869c76e63 Updated version in readme label (#295) 2020-02-19 17:48:49 +11:00
6f298ada61 Updated version in readme label 2020-02-19 16:48:18 +10:00
26a8e234fa Bump knex from 0.15.2 to 0.19.5 in /backend (#294)
Bumps [knex](https://github.com/knex/knex) from 0.15.2 to 0.19.5.
- [Release notes](https://github.com/knex/knex/releases)
- [Changelog](https://github.com/knex/knex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/knex/knex/compare/0.15.2...0.19.5)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: jc21 <jc@jc21.com>
2020-02-19 17:32:19 +11:00
43b5720532 Fix docker push for branches containing slashes, again 2020-02-19 16:11:52 +10:00
a25f196ceb Push to latest for master branch as well 2020-02-19 15:49:32 +10:00
f9e15a4470 Fix docker push for branches containing slashes 2020-02-19 15:46:52 +10:00
447cf12629 Fix master docker pushes, hopefully 2020-02-19 15:28:27 +10:00
bb0f4bfa62 v2.1.0 (#293)
* Fix wrapping when too many hosts are shown (#207)

* Update npm packages, fixes CVE-2019-10757

* Revert some breaking packages

* Major overhaul

- Docker buildx support in CI
- Cypress API Testing in CI
- Restructured folder layout (insert clean face meme)
- Added Swagger documentation and validate API against that (to be completed)
- Use common base image for all supported archs, which includes updated nginx with ipv6 support
- Updated certbot and changes required for it
- Large amount of Hosts names will wrap in UI
- Updated packages for frontend
- Version bump 2.1.0

* Updated documentation

* Fix JWT expire time going crazy. Now set to 1day

* Backend JS formatting rules

* Remove v1 importer, I doubt anyone is using v1 anymore

* Added backend formatting rules and enforce them
in Jenkins builds

* Fix CI, doesn't need a tty

* Thanks bcrypt. Why can't you just be normal.

* Cleanup after syntax check

Co-authored-by: Marcelo Castagna <margaale@users.noreply.github.com>
2020-02-19 15:55:06 +11:00
bf036cbb88 fix ipv6 resolver setup for nginx (#246) 2020-01-06 08:48:43 +10:00
8a9495de18 Update README.md 2019-11-29 09:46:41 +10:00
3e9614879a Update README.md 2019-11-29 09:43:03 +10:00
0057c793ee Updated Readme 2019-11-29 09:30:34 +10:00
ea5bdab374 Fix CI permissions, again and again 2019-10-10 09:50:46 +10:00
0e96ad62bb Fix CI permissions, again 2019-10-10 09:41:50 +10:00
aa26f3908b Fix CI permissions 2019-10-10 08:44:12 +10:00
d3f90647f5 Merge branch 'develop' of github.com:jc21/nginx-proxy-manager 2019-09-05 08:25:52 +10:00
7d6c6129f2 Updated readme versions 2019-09-05 08:25:30 +10:00
0bc12f3bdf Merge from develop v2.0.14 release (#196)
* Selection of the ssl protocols/ciphers is done using the default server block. (#189)
* Fix wrap for tags (#195)
2019-09-04 14:51:24 +10:00
13767d13d6 Version bump 2019-09-04 14:41:48 +10:00
a215958cec Fix wrap for tags (#195) 2019-09-04 14:40:59 +10:00
288086c78d Selection of the ssl protocols/ciphers is done using the default server block. (#189) 2019-08-21 10:52:00 +10:00
31aa9c9644 Allow including custom nginx conf files (#178)
* Allow including custom nginx conf files

Give advanced users more flexibility by allowing them to include custom config files at differents locations in the nginx configuration.

`/data/nginx/custom/root.conf`: Included at the very end of nginx.conf
`/data/nginx/custom/http.conf`: Included at the end of the main `http` block
`/data/nginx/custom/server_proxy.conf`: Included at the end of every proxy `server` block
`/data/nginx/custom/server_redirect.conf`: Included at the end of every redirection `server` block
`/data/nginx/custom/server_stream.conf`: Included at the end of every stream `server` block
`/data/nginx/custom/server_stream_tcp.conf`: Included at the end of every TCP stream `server` block
`/data/nginx/custom/server_stream_udp.conf`: Included at the end of every UDP stream `server` block

* Don't fail if file doesn't exist

* Advanced Nginx settings doc
2019-08-09 11:19:42 +10:00
ddbfdf6f6e Open up lets Encrypt acme challenge config (#165)
Since Lets Encrypt don't publish IP ranges that their acme challenge service will be sourced from, we need to allow free access to this location special to override any IP ACLs added by Advanced Custom Nginx Configuration. Due to the way Nginx config is applied, this only applies to the regex and below, keeping the IP ACLs working for the rest of the website.
2019-07-05 08:32:41 +10:00
43c7063538 Center username vertically (#163)
Center username/role container vertically within header
2019-06-25 15:25:45 +10:00
3f089fb239 Updated documentation, installation instructions and examples 2019-05-10 15:31:25 +10:00
2d0f7d5126 Updated documentation, installation instructions and examples 2019-05-10 15:26:12 +10:00
06272d3d2c Use correct var when returning updated certificate 2019-05-09 10:03:41 +10:00
3885c0ad6d Add cert renewals to audit log 2019-05-09 09:20:49 +10:00
099ec00155 Don't use LE staging when debug mode is on in production 2019-05-09 08:58:10 +10:00
92fcae9c54 Added missing dialog for renewing certs 2019-05-08 15:34:14 +10:00
22e8961c80 Fixes #104 - allow using / location in custom location 2019-05-08 15:33:54 +10:00
4d5adefa41 Added ability to force renew a LE cert, and also fix revoking certs 2019-05-08 15:25:48 +10:00
feaa0e51bd Removed use strict 2019-05-08 15:24:57 +10:00
af83cb57d0 Merge branch 'develop' of github.com:jc21/nginx-proxy-manager into develop 2019-05-08 11:23:50 +10:00
8b4f3507c3 Revert to previous tabler version to hopefully fix ui issues 2019-05-08 11:21:59 +10:00
bda3dba369 Revert to previous tabler version to hopefully fix ui issues 2019-05-08 10:53:44 +10:00
beb313af40 Merge branch 'master' of github.com:jc21/nginx-proxy-manager into develop 2019-05-08 10:11:32 +10:00
4fad9d672f Correcting X-XSS-Protection Header (#136)
* Correcting X-XSS-Protection Header

X-XSS-Protection sets the configuration for the cross-site scripting filters built into most browsers. The best configuration is "X-XSS-Protection: 1; mode=block".

Was "0"
Now "1; mode=block"

* Update issue templates
2019-05-08 10:11:05 +10:00
0fca64929e Try DNS challenge in addition to http (#85) 2019-05-08 10:07:43 +10:00
9e476e5b24 Only Secure TLS Ciphers & Protocols (#134)
Disable insecure SSL/TLS ciphers & protocols. Only TLS_1.2 and TLS_1.3 should be enabled.
2019-05-08 10:01:08 +10:00
0819a265f5 Bumped version 2019-05-08 09:50:20 +10:00
ad8eac4f07 Update issue templates 2019-05-08 09:36:44 +10:00
b49de0e23e Enable TLS 1.3 by default 2019-05-02 13:03:16 +10:00
efbd024da9 Update copyright year (#121)
Updated the year in the copyright statement in the footer
2019-04-20 21:28:06 +10:00
e7ddcb91fc Fixed directory traversal vulnerability. (#114)
Awesome find!
2019-04-03 08:37:40 +10:00
3095cff7d9 Forgot to change dockerfiles to match CI names 2019-03-19 07:01:12 +10:00
6d8f5aa3a7 Version bump 2019-03-15 07:49:08 +10:00
27a06850ff CI Docker manifest improvements 2019-03-15 07:49:08 +10:00
dce6423c85 Fixes #103 - Allow for longer domain names 2019-03-15 07:49:08 +10:00
d79fcbf447 This commit resolves #98 so custom location can forward to custom path. (#99)
Awesome work!
2019-03-11 13:52:09 +10:00
631d9ae4eb CI Changes, docker image tag changes and manifests 2019-03-07 09:45:01 +10:00
0ac349ba67 CI Changes, docker image tag changes and manifests 2019-03-07 09:45:01 +10:00
1b0563a4a6 CI Changes, docker image tag changes and manifests 2019-03-07 09:45:01 +10:00
1db2a29d49 CI Changes, docker image tag changes and manifests 2019-03-07 09:45:01 +10:00
14e62a0830 CI Changes, docker image tag changes and manifests 2019-03-07 09:45:01 +10:00
2280a61c2b CI Fix for arm64 2019-03-05 08:25:12 +10:00
611 changed files with 43318 additions and 13013 deletions

View File

@ -1,12 +0,0 @@
{
"presets": [
["env", {
"targets": {
"browsers": ["Chrome >= 65"]
},
"debug": false,
"modules": false,
"useBuiltIns": "usage"
}]
]
}

55
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,55 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
<!--
Are you in the right place?
- If you are looking for support on how to get your upstream server forwarding, please consider asking the community on Reddit.
- If you are writing code changes to contribute and need to ask about the internals of the software, Gitter is the best place to ask.
- If you think you found a bug with NPM (not Nginx, or your upstream server or MySql) then you are in the *right place.*
-->
**Checklist**
- Have you pulled and found the error with `jc21/nginx-proxy-manager:latest` docker image?
- Yes / No
- Are you sure you're not using someone else's docker image?
- Yes / No
- Have you searched for similar issues (both open and closed)?
- Yes / No
**Describe the bug**
<!-- A clear and concise description of what the bug is. -->
**Nginx Proxy Manager Version**
<!-- What version of Nginx Proxy Manager is reported on the login page? -->
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
<!-- A clear and concise description of what you expected to happen. -->
**Screenshots**
<!-- If applicable, add screenshots to help explain your problem. -->
**Operating System**
<!-- Please specify if using a Rpi, Mac, orchestration tool or any other setups that might affect the reproduction of this error. -->
**Additional context**
<!-- Add any other context about the problem here, docker version, browser version, logs if applicable to the problem. Too much info is better than too little. -->

View File

@ -0,0 +1,18 @@
---
name: DNS challenge provider request
about: Suggest a new provider to be available for a certificate DNS challenge
title: ''
labels: dns provider request
assignees: ''
---
**What provider would you like to see added to NPM?**
<!-- What is this provider called? -->
**Have you checked if a certbot plugin exists?**
<!--
Currently NPM only supports DNS challenge providers for which a certbot plugin exists.
You can visit pypi.org, and search for a package with the name `certbot-dns-<privider>`.
-->

View File

@ -0,0 +1,32 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
<!--
Are you in the right place?
- If you are looking for support on how to get your upstream server forwarding, please consider asking the community on Reddit.
- If you are writing code changes to contribute and need to ask about the internals of the software, Gitter is the best place to ask.
- If you think you found a bug with NPM (not Nginx, or your upstream server or MySql) then you are in the *right place.*
-->
**Is your feature request related to a problem? Please describe.**
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
**Describe the solution you'd like**
<!-- A clear and concise description of what you want to happen. -->
**Describe alternatives you've considered**
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
**Additional context**
<!-- Add any other context or screenshots about the feature request here. -->

13
.gitignore vendored
View File

@ -1,14 +1,5 @@
.DS_Store
.idea
._*
node_modules
core*
config/development.json
dist
webpack_stats.html
data/*
yarn-error.log
yarn.lock
tmp
certbot.log
.vscode
certbot-help.txt

1
.version Normal file
View File

@ -0,0 +1 @@
2.9.13

View File

@ -1,39 +0,0 @@
FROM jc21/nginx-proxy-manager-base:latest
MAINTAINER Jamie Curnow <jc@jc21.com>
LABEL maintainer="Jamie Curnow <jc@jc21.com>"
ENV SUPPRESS_NO_CONFIG_WARNING=1
ENV S6_FIX_ATTRS_HIDDEN=1
RUN echo "fs.file-max = 65535" > /etc/sysctl.conf
# Nginx, Node and required packages should already be installed from the base image
# root filesystem
COPY rootfs /
# s6 overlay
RUN curl -L -o /tmp/s6-overlay-amd64.tar.gz "https://github.com/just-containers/s6-overlay/releases/download/v1.21.4.0/s6-overlay-amd64.tar.gz" \
&& tar xzf /tmp/s6-overlay-amd64.tar.gz -C /
# App
ENV NODE_ENV=production
ADD dist /app/dist
ADD node_modules /app/node_modules
ADD src/backend /app/src/backend
ADD package.json /app/package.json
ADD knexfile.js /app/knexfile.js
# Volumes
VOLUME [ "/data", "/etc/letsencrypt" ]
CMD [ "/init" ]
# Ports
EXPOSE 80
EXPOSE 81
EXPOSE 443
EXPOSE 9876
HEALTHCHECK --interval=15s --timeout=3s CMD curl -f http://localhost:9876/health || exit 1

View File

@ -1,38 +0,0 @@
FROM jc21/nginx-proxy-manager-base:arm64
MAINTAINER Jamie Curnow <jc@jc21.com>
LABEL maintainer="Jamie Curnow <jc@jc21.com>"
ENV SUPPRESS_NO_CONFIG_WARNING=1
ENV S6_FIX_ATTRS_HIDDEN=1
RUN echo "fs.file-max = 65535" > /etc/sysctl.conf
# Nginx, Node and required packages should already be installed from the base image
# root filesystem
COPY rootfs /
# s6 overlay
RUN curl -L -o /tmp/s6-overlay-aarch64.tar.gz "https://github.com/just-containers/s6-overlay/releases/download/v1.21.8.0/s6-overlay-aarch64.tar.gz" \
&& tar xzf /tmp/s6-overlay-aarch64.tar.gz -C /
# App
ENV NODE_ENV=production
ADD dist /app/dist
ADD node_modules /app/node_modules
ADD src/backend /app/src/backend
ADD package.json /app/package.json
ADD knexfile.js /app/knexfile.js
# Volumes
VOLUME [ "/data", "/etc/letsencrypt" ]
CMD [ "/init" ]
# Ports
EXPOSE 80
EXPOSE 81
EXPOSE 443
EXPOSE 9876
HEALTHCHECK --interval=15s --timeout=3s CMD curl -f http://localhost:9876/health || exit 1

View File

@ -1,38 +0,0 @@
FROM jc21/nginx-proxy-manager-base:armhf
MAINTAINER Jamie Curnow <jc@jc21.com>
LABEL maintainer="Jamie Curnow <jc@jc21.com>"
ENV SUPPRESS_NO_CONFIG_WARNING=1
ENV S6_FIX_ATTRS_HIDDEN=1
RUN echo "fs.file-max = 65535" > /etc/sysctl.conf
# Nginx, Node and required packages should already be installed from the base image
# root filesystem
COPY rootfs /
# s6 overlay
RUN curl -L -o /tmp/s6-overlay-armhf.tar.gz "https://github.com/just-containers/s6-overlay/releases/download/v1.21.4.0/s6-overlay-armhf.tar.gz" \
&& tar xzf /tmp/s6-overlay-armhf.tar.gz -C /
# App
ENV NODE_ENV=production
ADD dist /app/dist
ADD node_modules /app/node_modules
ADD src/backend /app/src/backend
ADD package.json /app/package.json
ADD knexfile.js /app/knexfile.js
# Volumes
VOLUME [ "/data", "/etc/letsencrypt" ]
CMD [ "/init" ]
# Ports
EXPOSE 80
EXPOSE 81
EXPOSE 443
EXPOSE 9876
HEALTHCHECK --interval=15s --timeout=3s CMD curl -f http://localhost:9876/health || exit 1

View File

@ -1,38 +0,0 @@
FROM jc21/nginx-proxy-manager-base:armv6
MAINTAINER Jamie Curnow <jc@jc21.com>
LABEL maintainer="Jamie Curnow <jc@jc21.com>"
ENV SUPPRESS_NO_CONFIG_WARNING=1
ENV S6_FIX_ATTRS_HIDDEN=1
RUN echo "fs.file-max = 65535" > /etc/sysctl.conf
# Nginx, Node and required packages should already be installed from the base image
# root filesystem
COPY rootfs /
# s6 overlay
RUN curl -L -o /tmp/s6-overlay-arm.tar.gz "https://github.com/just-containers/s6-overlay/releases/download/v1.21.8.0/s6-overlay-arm.tar.gz" \
&& tar xzf /tmp/s6-overlay-arm.tar.gz -C /
# App
ENV NODE_ENV=production
ADD dist /app/dist
ADD node_modules /app/node_modules
ADD src/backend /app/src/backend
ADD package.json /app/package.json
ADD knexfile.js /app/knexfile.js
# Volumes
VOLUME [ "/data", "/etc/letsencrypt" ]
CMD [ "/init" ]
# Ports
EXPOSE 80
EXPOSE 81
EXPOSE 443
EXPOSE 9876
HEALTHCHECK --interval=15s --timeout=3s CMD curl -f http://localhost:9876/health || exit 1

467
Jenkinsfile vendored
View File

@ -1,237 +1,252 @@
pipeline {
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
disableConcurrentBuilds()
}
agent any
environment {
IMAGE = "nginx-proxy-manager"
BASE_IMAGE = "jc21/nginx-proxy-manager-base"
TEMP_IMAGE = "nginx-proxy-manager-build_${BUILD_NUMBER}"
TEMP_IMAGE_ARM = "nginx-proxy-manager-arm-build_${BUILD_NUMBER}"
TEMP_IMAGE_ARM64 = "nginx-proxy-manager-arm64-build_${BUILD_NUMBER}"
TAG_VERSION = getPackageVersion()
MAJOR_VERSION = "2"
BRANCH_LOWER = "${BRANCH_NAME.toLowerCase()}"
}
stages {
stage('Build PR') {
when {
changeRequest()
}
steps {
ansiColor('xterm') {
// Codebase
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE} yarn install'
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE} npm run-script build'
sh 'rm -rf node_modules'
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE} yarn install --prod'
sh 'docker run --rm -v $(pwd):/data ${DOCKER_CI_TOOLS} node-prune'
agent {
label 'docker-multiarch'
}
options {
buildDiscarder(logRotator(numToKeepStr: '5'))
disableConcurrentBuilds()
ansiColor('xterm')
}
environment {
IMAGE = "nginx-proxy-manager"
BUILD_VERSION = getVersion()
MAJOR_VERSION = "2"
BRANCH_LOWER = "${BRANCH_NAME.toLowerCase().replaceAll('/', '-')}"
COMPOSE_PROJECT_NAME = "npm_${BRANCH_LOWER}_${BUILD_NUMBER}"
COMPOSE_FILE = 'docker/docker-compose.ci.yml'
COMPOSE_INTERACTIVE_NO_CLI = 1
BUILDX_NAME = "${COMPOSE_PROJECT_NAME}"
}
stages {
stage('Environment') {
parallel {
stage('Master') {
when {
branch 'master'
}
steps {
script {
env.BUILDX_PUSH_TAGS = "-t docker.io/jc21/${IMAGE}:${BUILD_VERSION} -t docker.io/jc21/${IMAGE}:${MAJOR_VERSION} -t docker.io/jc21/${IMAGE}:latest"
}
}
}
stage('Other') {
when {
not {
branch 'master'
}
}
steps {
script {
// Defaults to the Branch name, which is applies to all branches AND pr's
env.BUILDX_PUSH_TAGS = "-t docker.io/jc21/${IMAGE}:github-${BRANCH_LOWER}"
}
}
}
stage('Versions') {
steps {
sh 'cat frontend/package.json | jq --arg BUILD_VERSION "${BUILD_VERSION}" \'.version = $BUILD_VERSION\' | sponge frontend/package.json'
sh 'echo -e "\\E[1;36mFrontend Version is:\\E[1;33m $(cat frontend/package.json | jq -r .version)\\E[0m"'
sh 'cat backend/package.json | jq --arg BUILD_VERSION "${BUILD_VERSION}" \'.version = $BUILD_VERSION\' | sponge backend/package.json'
sh 'echo -e "\\E[1;36mBackend Version is:\\E[1;33m $(cat backend/package.json | jq -r .version)\\E[0m"'
sh 'sed -i -E "s/(version-)[0-9]+\\.[0-9]+\\.[0-9]+(-green)/\\1${BUILD_VERSION}\\2/" README.md'
}
}
}
}
stage('Frontend') {
steps {
sh './scripts/frontend-build'
}
}
stage('Backend') {
steps {
echo 'Checking Syntax ...'
// See: https://github.com/yarnpkg/yarn/issues/3254
sh '''docker run --rm \\
-v "$(pwd)/backend:/app" \\
-v "$(pwd)/global:/app/global" \\
-w /app \\
node:latest \\
sh -c "ln -s /usr/bin/python3 /usr/bin/python && yarn install && yarn eslint . && rm -rf node_modules"
'''
// Docker Build
sh 'docker build --pull --no-cache --squash --compress -t ${TEMP_IMAGE} .'
echo 'Docker Build ...'
sh '''docker build --pull --no-cache --squash --compress \\
-t "${IMAGE}:ci-${BUILD_NUMBER}" \\
-f docker/Dockerfile \\
--build-arg TARGETPLATFORM=linux/amd64 \\
--build-arg BUILDPLATFORM=linux/amd64 \\
--build-arg BUILD_VERSION="${BUILD_VERSION}" \\
--build-arg BUILD_COMMIT="${BUILD_COMMIT}" \\
--build-arg BUILD_DATE="$(date '+%Y-%m-%d %T %Z')" \\
.
'''
}
}
stage('Integration Tests Sqlite') {
steps {
// Bring up a stack
sh 'docker-compose up -d fullstack-sqlite'
sh './scripts/wait-healthy $(docker-compose ps -q fullstack-sqlite) 120'
// Dockerhub
sh 'docker tag ${TEMP_IMAGE} docker.io/jc21/${IMAGE}:github-${BRANCH_LOWER}'
withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) {
sh "docker login -u '${duser}' -p '${dpass}'"
sh 'docker push docker.io/jc21/${IMAGE}:github-${BRANCH_LOWER}'
}
// Run tests
sh 'rm -rf test/results'
sh 'docker-compose up cypress-sqlite'
// Get results
sh 'docker cp -L "$(docker-compose ps -q cypress-sqlite):/test/results" test/'
}
post {
always {
// Dumps to analyze later
sh 'mkdir -p debug'
sh 'docker-compose logs fullstack-sqlite | gzip > debug/docker_fullstack_sqlite.log.gz'
sh 'docker-compose logs db | gzip > debug/docker_db.log.gz'
// Cypress videos and screenshot artifacts
dir(path: 'test/results') {
archiveArtifacts allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml'
}
junit 'test/results/junit/*'
}
}
}
stage('Integration Tests Mysql') {
steps {
// Bring up a stack
sh 'docker-compose up -d fullstack-mysql'
sh './scripts/wait-healthy $(docker-compose ps -q fullstack-mysql) 120'
sh 'docker rmi ${TEMP_IMAGE}'
// Run tests
sh 'rm -rf test/results'
sh 'docker-compose up cypress-mysql'
// Get results
sh 'docker cp -L "$(docker-compose ps -q cypress-mysql):/test/results" test/'
}
post {
always {
// Dumps to analyze later
sh 'mkdir -p debug'
sh 'docker-compose logs fullstack-mysql | gzip > debug/docker_fullstack_mysql.log.gz'
sh 'docker-compose logs db | gzip > debug/docker_db.log.gz'
// Cypress videos and screenshot artifacts
dir(path: 'test/results') {
archiveArtifacts allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml'
}
junit 'test/results/junit/*'
}
}
}
stage('Docs') {
when {
not {
equals expected: 'UNSTABLE', actual: currentBuild.result
}
}
steps {
dir(path: 'docs') {
sh 'yarn install'
sh 'yarn build'
}
script {
def comment = pullRequest.comment("Docker Image for build ${BUILD_NUMBER} is available on [DockerHub](https://cloud.docker.com/repository/docker/jc21/${IMAGE}) as `jc21/${IMAGE}:github-${BRANCH_LOWER}`")
}
}
}
}
stage('Build Develop') {
when {
branch 'develop'
}
steps {
ansiColor('xterm') {
// Codebase
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE} yarn install'
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE} npm run-script build'
sh 'rm -rf node_modules'
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE} yarn install --prod'
sh 'docker run --rm -v $(pwd):/data ${DOCKER_CI_TOOLS} node-prune'
dir(path: 'docs/.vuepress/dist') {
sh 'tar -czf ../../docs.tgz *'
}
// Docker Build
sh 'docker build --pull --no-cache --squash --compress -t ${TEMP_IMAGE} .'
archiveArtifacts(artifacts: 'docs/docs.tgz', allowEmptyArchive: false)
}
}
stage('MultiArch Build') {
when {
not {
equals expected: 'UNSTABLE', actual: currentBuild.result
}
}
steps {
withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) {
// Docker Login
sh "docker login -u '${duser}' -p '${dpass}'"
// Buildx with push from cache
sh "./scripts/buildx --push ${BUILDX_PUSH_TAGS}"
}
}
}
stage('Docs Deploy') {
when {
allOf {
branch 'master'
not {
equals expected: 'UNSTABLE', actual: currentBuild.result
}
}
}
steps {
withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', accessKeyVariable: 'AWS_ACCESS_KEY_ID', credentialsId: 'npm-s3-docs', secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) {
sh """docker run --rm \\
--name \${COMPOSE_PROJECT_NAME}-docs-upload \\
-e S3_BUCKET=jc21-npm-site \\
-e AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \\
-e AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \\
-v \$(pwd):/app \\
-w /app \\
jc21/ci-tools \\
scripts/docs-upload /app/docs/.vuepress/dist/
"""
// Dockerhub
sh 'docker tag ${TEMP_IMAGE} docker.io/jc21/${IMAGE}:develop'
withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) {
sh "docker login -u '${duser}' -p '${dpass}'"
sh 'docker push docker.io/jc21/${IMAGE}:develop'
}
// Private Registry
sh 'docker tag ${TEMP_IMAGE} ${DOCKER_PRIVATE_REGISTRY}/${IMAGE}:develop'
withCredentials([usernamePassword(credentialsId: 'jc21-private-registry', passwordVariable: 'dpass', usernameVariable: 'duser')]) {
sh "docker login -u '${duser}' -p '${dpass}' ${DOCKER_PRIVATE_REGISTRY}"
sh 'docker push ${DOCKER_PRIVATE_REGISTRY}/${IMAGE}:develop'
}
sh 'docker rmi ${TEMP_IMAGE}'
}
}
}
stage('Build Master') {
when {
branch 'master'
}
parallel {
stage('x86_64') {
steps {
ansiColor('xterm') {
// Codebase
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE} yarn install'
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE} npm run-script build'
sh 'rm -rf node_modules'
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE} yarn install --prod'
sh 'docker run --rm -v $(pwd):/data ${DOCKER_CI_TOOLS} node-prune'
// Docker Build
sh 'docker build --pull --no-cache --squash --compress -t ${TEMP_IMAGE} .'
// Dockerhub
sh 'docker tag ${TEMP_IMAGE} docker.io/jc21/${IMAGE}:${TAG_VERSION}'
sh 'docker tag ${TEMP_IMAGE} docker.io/jc21/${IMAGE}:${MAJOR_VERSION}'
sh 'docker tag ${TEMP_IMAGE} docker.io/jc21/${IMAGE}:latest'
withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) {
sh "docker login -u '${duser}' -p '${dpass}'"
sh 'docker push docker.io/jc21/${IMAGE}:${TAG_VERSION}'
sh 'docker push docker.io/jc21/${IMAGE}:${MAJOR_VERSION}'
sh 'docker push docker.io/jc21/${IMAGE}:latest'
}
// Private Registry
sh 'docker tag ${TEMP_IMAGE} ${DOCKER_PRIVATE_REGISTRY}/${IMAGE}:${TAG_VERSION}'
sh 'docker tag ${TEMP_IMAGE} ${DOCKER_PRIVATE_REGISTRY}/${IMAGE}:${MAJOR_VERSION}'
sh 'docker tag ${TEMP_IMAGE} ${DOCKER_PRIVATE_REGISTRY}/${IMAGE}:latest'
withCredentials([usernamePassword(credentialsId: 'jc21-private-registry', passwordVariable: 'dpass', usernameVariable: 'duser')]) {
sh "docker login -u '${duser}' -p '${dpass}' ${DOCKER_PRIVATE_REGISTRY}"
sh 'docker push ${DOCKER_PRIVATE_REGISTRY}/${IMAGE}:${TAG_VERSION}'
sh 'docker push ${DOCKER_PRIVATE_REGISTRY}/${IMAGE}:${MAJOR_VERSION}'
sh 'docker push ${DOCKER_PRIVATE_REGISTRY}/${IMAGE}:latest'
}
sh 'docker rmi ${TEMP_IMAGE}'
}
}
}
stage('armhf') {
agent {
label 'armhf'
}
steps {
ansiColor('xterm') {
// Codebase
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE}:armhf yarn install'
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE}:armhf npm run-script build'
sh 'rm -rf node_modules'
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE}:armhf yarn install --prod'
// Docker Build
sh 'docker build --pull --no-cache --squash --compress -t ${TEMP_IMAGE_ARM} -f Dockerfile.armhf .'
// Dockerhub
sh 'docker tag ${TEMP_IMAGE_ARM} docker.io/jc21/${IMAGE}:${TAG_VERSION}-armhf'
sh 'docker tag ${TEMP_IMAGE_ARM} docker.io/jc21/${IMAGE}:${MAJOR_VERSION}-armhf'
sh 'docker tag ${TEMP_IMAGE_ARM} docker.io/jc21/${IMAGE}:latest-armhf'
withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) {
sh "docker login -u '${duser}' -p '${dpass}'"
sh 'docker push docker.io/jc21/${IMAGE}:${TAG_VERSION}-armhf'
sh 'docker push docker.io/jc21/${IMAGE}:${MAJOR_VERSION}-armhf'
sh 'docker push docker.io/jc21/${IMAGE}:latest-armhf'
}
// Private Registry
sh 'docker tag ${TEMP_IMAGE_ARM} ${DOCKER_PRIVATE_REGISTRY}/${IMAGE}:${TAG_VERSION}-armhf'
sh 'docker tag ${TEMP_IMAGE_ARM} ${DOCKER_PRIVATE_REGISTRY}/${IMAGE}:${MAJOR_VERSION}-armhf'
sh 'docker tag ${TEMP_IMAGE_ARM} ${DOCKER_PRIVATE_REGISTRY}/${IMAGE}:latest-armhf'
withCredentials([usernamePassword(credentialsId: 'jc21-private-registry', passwordVariable: 'dpass', usernameVariable: 'duser')]) {
sh "docker login -u '${duser}' -p '${dpass}' ${DOCKER_PRIVATE_REGISTRY}"
sh 'docker push ${DOCKER_PRIVATE_REGISTRY}/${IMAGE}:${TAG_VERSION}-armhf'
sh 'docker push ${DOCKER_PRIVATE_REGISTRY}/${IMAGE}:${MAJOR_VERSION}-armhf'
sh 'docker push ${DOCKER_PRIVATE_REGISTRY}/${IMAGE}:latest-armhf'
}
sh 'docker rmi ${TEMP_IMAGE_ARM}'
}
}
}
stage('arm64') {
agent {
label 'arm64'
}
steps {
ansiColor('xterm') {
// Codebase
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE}:arm64 yarn install'
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE}:arm64 npm run-script build'
sh 'sudo rm -rf node_modules'
sh 'docker run --rm -v $(pwd):/app -w /app ${BASE_IMAGE}:arm64 yarn install --prod'
// Docker Build
sh 'docker build --pull --no-cache --squash --compress -t ${TEMP_IMAGE_ARM64} -f Dockerfile.arm64 .'
// Dockerhub
sh 'docker tag ${TEMP_IMAGE_ARM64} docker.io/jc21/${IMAGE}:${TAG_VERSION}-arm64'
sh 'docker tag ${TEMP_IMAGE_ARM64} docker.io/jc21/${IMAGE}:${MAJOR_VERSION}-arm64'
sh 'docker tag ${TEMP_IMAGE_ARM64} docker.io/jc21/${IMAGE}:latest-arm64'
withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) {
sh "docker login -u '${duser}' -p '${dpass}'"
sh 'docker push docker.io/jc21/${IMAGE}:${TAG_VERSION}-arm64'
sh 'docker push docker.io/jc21/${IMAGE}:${MAJOR_VERSION}-arm64'
sh 'docker push docker.io/jc21/${IMAGE}:latest-arm64'
}
// Private Registry
sh 'docker tag ${TEMP_IMAGE_ARM64} ${DOCKER_PRIVATE_REGISTRY}/${IMAGE}:${TAG_VERSION}-arm64'
sh 'docker tag ${TEMP_IMAGE_ARM64} ${DOCKER_PRIVATE_REGISTRY}/${IMAGE}:${MAJOR_VERSION}-arm64'
sh 'docker tag ${TEMP_IMAGE_ARM64} ${DOCKER_PRIVATE_REGISTRY}/${IMAGE}:latest-arm64'
withCredentials([usernamePassword(credentialsId: 'jc21-private-registry', passwordVariable: 'dpass', usernameVariable: 'duser')]) {
sh "docker login -u '${duser}' -p '${dpass}' ${DOCKER_PRIVATE_REGISTRY}"
sh 'docker push ${DOCKER_PRIVATE_REGISTRY}/${IMAGE}:${TAG_VERSION}-arm64'
sh 'docker push ${DOCKER_PRIVATE_REGISTRY}/${IMAGE}:${MAJOR_VERSION}-arm64'
sh 'docker push ${DOCKER_PRIVATE_REGISTRY}/${IMAGE}:latest-arm64'
}
sh 'docker rmi ${TEMP_IMAGE_ARM64}'
// Hack to clean up ec2 instance for next build
sh 'sudo chown -R ec2-user:ec2-user *'
}
}
}
}
}
}
post {
success {
juxtapose event: 'success'
sh 'figlet "SUCCESS"'
}
failure {
juxtapose event: 'failure'
sh 'figlet "FAILURE"'
}
}
sh """docker run --rm \\
--name \${COMPOSE_PROJECT_NAME}-docs-invalidate \\
-e AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \\
-e AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \\
jc21/ci-tools \\
aws cloudfront create-invalidation --distribution-id EN1G6DEWZUTDT --paths '/*'
"""
}
}
}
stage('PR Comment') {
when {
allOf {
changeRequest()
not {
equals expected: 'UNSTABLE', actual: currentBuild.result
}
}
}
steps {
script {
def comment = pullRequest.comment("This is an automated message from CI:\n\nDocker 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.")
}
}
}
}
post {
always {
sh 'docker-compose down --rmi all --remove-orphans --volumes -t 30'
sh 'echo Reverting ownership'
sh 'docker run --rm -v $(pwd):/data jc21/ci-tools chown -R $(id -u):$(id -g) /data'
}
success {
juxtapose event: 'success'
sh 'figlet "SUCCESS"'
}
failure {
archiveArtifacts(artifacts: 'debug/**.*', allowEmptyArchive: true)
juxtapose event: 'failure'
sh 'figlet "FAILURE"'
}
unstable {
archiveArtifacts(artifacts: 'debug/**.*', allowEmptyArchive: true)
juxtapose event: 'unstable'
sh 'figlet "UNSTABLE"'
}
}
}
def getPackageVersion() {
ver = sh(script: 'docker run --rm -v $(pwd):/data ${DOCKER_CI_TOOLS} bash -c "cat /data/package.json|jq -r \'.version\'"', returnStdout: true)
return ver.trim()
def getVersion() {
ver = sh(script: 'cat .version', returnStdout: true)
return ver.trim()
}
def getCommit() {
ver = sh(script: 'git log -n 1 --format=%h', returnStdout: true)
return ver.trim()
}

528
README.md
View File

@ -1,15 +1,31 @@
![Nginx Proxy Manager](https://public.jc21.com/nginx-proxy-manager/github.png "Nginx Proxy Manager")
# Nginx Proxy Manager
![Version](https://img.shields.io/badge/version-2.0.11-green.svg?style=for-the-badge)
![Stars](https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge)
![Pulls](https://img.shields.io/docker/pulls/jc21/nginx-proxy-manager.svg?style=for-the-badge)
<p align="center">
<img src="https://nginxproxymanager.com/github.png">
<br><br>
<img src="https://img.shields.io/badge/version-2.9.13-green.svg?style=for-the-badge">
<a href="https://hub.docker.com/repository/docker/jc21/nginx-proxy-manager">
<img src="https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge">
</a>
<a href="https://hub.docker.com/repository/docker/jc21/nginx-proxy-manager">
<img src="https://img.shields.io/docker/pulls/jc21/nginx-proxy-manager.svg?style=for-the-badge">
</a>
<a href="https://ci.nginxproxymanager.com/blue/organizations/jenkins/nginx-proxy-manager/branches/">
<img src="https://img.shields.io/jenkins/build?jobUrl=https%3A%2F%2Fci.nginxproxymanager.com%2Fjob%2Fnginx-proxy-manager%2Fjob%2Fmaster&style=for-the-badge">
</a>
<a href="https://gitter.im/nginx-proxy-manager/community">
<img alt="Gitter" src="https://img.shields.io/gitter/room/nginx-proxy-manager/community?style=for-the-badge">
</a>
<a href="https://reddit.com/r/nginxproxymanager">
<img alt="Reddit" src="https://img.shields.io/reddit/subreddit-subscribers/nginxproxymanager?label=Reddit%20Community&style=for-the-badge">
</a>
</p>
This project comes as a pre-built docker image that enables you to easily forward to your websites
running at home or otherwise, including free SSL, without having to know too much about Nginx or Letsencrypt.
- [Quick Setup](#quick-setup)
- [Full Setup](https://nginxproxymanager.com/setup/)
- [Screenshots](https://nginxproxymanager.com/screenshots/)
## Project Goal
I created this project to fill a personal need to provide users with a easy way to accomplish reverse
@ -17,71 +33,465 @@ proxying hosts with SSL termination and it had to be so easy that a monkey could
While there might be advanced options they are optional and the project should be as simple as possible
so that the barrier for entry here is low.
<a href="https://www.buymeacoffee.com/jc21" target="_blank"><img src="http://public.jc21.com/github/by-me-a-coffee.png" alt="Buy Me A Coffee" style="height: 51px !important;width: 217px !important;" ></a>
## Features
- Beautiful and Secure Admin Interface based on [Tabler](https://tabler.github.io/)
- Easily create forwarding domains, redirections, streams and 404 hosts without knowing anything about Nginx
- Free SSL using Let's Encrypt or provide your own custom SSL certificates
- Free SSL using Let's Encrypt or provide your own custom SSL certificates
- Access Lists and basic HTTP Authentication for your hosts
- Advanced Nginx configuration available for super users
- User management, permissions and audit log
## Screenshots
[![Login](https://public.jc21.com/nginx-proxy-manager/v2/small/login.jpg "Login")](https://public.jc21.com/nginx-proxy-manager/v2/large/login.jpg)
[![Dashboard](https://public.jc21.com/nginx-proxy-manager/v2/small/dashboard.jpg "Dashboard")](https://public.jc21.com/nginx-proxy-manager/v2/large/dashboard.jpg)
[![Proxy Hosts](https://public.jc21.com/nginx-proxy-manager/v2/small/proxy-hosts.jpg "Proxy Hosts")](https://public.jc21.com/nginx-proxy-manager/v2/large/proxy-hosts.jpg)
[![Proxy Host Settings](https://public.jc21.com/nginx-proxy-manager/v2/small/proxy-hosts-new1.jpg "Proxy Host Settings")](https://public.jc21.com/nginx-proxy-manager/v2/large/proxy-hosts-new1.jpg)
[![Proxy Host SSL](https://public.jc21.com/nginx-proxy-manager/v2/small/proxy-hosts-new2.jpg "Proxy Host SSL")](https://public.jc21.com/nginx-proxy-manager/v2/large/proxy-hosts-new2.jpg)
[![Redirection Hosts](https://public.jc21.com/nginx-proxy-manager/v2/small/redirection-hosts.jpg "Redirection Hosts")](https://public.jc21.com/nginx-proxy-manager/v2/large/redirection-hosts.jpg)
[![Redirection Host Settings](https://public.jc21.com/nginx-proxy-manager/v2/small/redirection-hosts-new1.jpg "Redirection Host Settings")](https://public.jc21.com/nginx-proxy-manager/v2/large/redirection-hosts-new1.jpg)
[![Streams](https://public.jc21.com/nginx-proxy-manager/v2/small/streams.jpg "Streams")](https://public.jc21.com/nginx-proxy-manager/v2/large/streams.jpg)
[![Stream Settings](https://public.jc21.com/nginx-proxy-manager/v2/small/streams-new1.jpg "Stream Settings")](https://public.jc21.com/nginx-proxy-manager/v2/large/streams-new1.jpg)
[![404 Hosts](https://public.jc21.com/nginx-proxy-manager/v2/small/dead-hosts.jpg "404 Hosts")](https://public.jc21.com/nginx-proxy-manager/v2/large/dead-hosts.jpg)
[![404 Host Settings](https://public.jc21.com/nginx-proxy-manager/v2/small/dead-hosts-new1.jpg "404 Host Settings")](https://public.jc21.com/nginx-proxy-manager/v2/large/dead-hosts-new1.jpg)
[![Certificates](https://public.jc21.com/nginx-proxy-manager/v2/small/certificates.jpg "Certificates")](https://public.jc21.com/nginx-proxy-manager/v2/large/certificates.jpg)
[![Lets Encrypt Certificates](https://public.jc21.com/nginx-proxy-manager/v2/small/certificates-new1.jpg "Lets Encrypt Certificates")](https://public.jc21.com/nginx-proxy-manager/v2/large/certificates-new1.jpg)
[![Custom Certificates](https://public.jc21.com/nginx-proxy-manager/v2/small/certificates-new2.jpg "Custom Certificates")](https://public.jc21.com/nginx-proxy-manager/v2/large/certificates-new2.jpg)
[![Access Lists](https://public.jc21.com/nginx-proxy-manager/v2/small/access-lists.jpg "Access Lists")](https://public.jc21.com/nginx-proxy-manager/v2/large/access-lists.jpg)
[![Access List Users](https://public.jc21.com/nginx-proxy-manager/v2/small/access-lists-new1.jpg "Access List Users")](https://public.jc21.com/nginx-proxy-manager/v2/large/access-lists-new1.jpg)
[![Users](https://public.jc21.com/nginx-proxy-manager/v2/small/users.jpg "Users")](https://public.jc21.com/nginx-proxy-manager/v2/large/users.jpg)
[![User Permissions](https://public.jc21.com/nginx-proxy-manager/v2/small/users-permissions.jpg "User Permissions")](https://public.jc21.com/nginx-proxy-manager/v2/large/users-permissions.jpg)
[![Audit Log](https://public.jc21.com/nginx-proxy-manager/v2/small/audit-log.jpg "Audit Log")](https://public.jc21.com/nginx-proxy-manager/v2/large/audit-log.jpg)
## Getting started
Please consult the [installation instructions](doc/INSTALL.md) for a complete guide or
if you just want to get up and running in the quickest time possible, grab all the files in the `doc/example/` folder and run `docker-compose up -d`
## Administration
When your docker container is running, connect to it on port `81` for the admin interface.
[http://localhost:81](http://localhost:81)
Note: Requesting SSL Certificates won't work until this project is accessible from the outside world, as explained below.
### Default Administrator User
```
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.
## Hosting your home network
I won't go in to too much detail here but here are the basics for someone new to this self-hosted world.
1. Your home router will have a Port Forwarding section somewhere. Log in and find it
2. Add port forwarding for port 80 and 443 to the server hosting this project
3. Configure your domain name details to point to your home, either with a static ip or a service like DuckDNS
4. Use the Nginx Proxy Manager here as your gateway to forward to your other web based services
3. Configure your domain name details to point to your home, either with a static ip or a service like DuckDNS or [Amazon Route53](https://github.com/jc21/route53-ddns)
4. Use the Nginx Proxy Manager as your gateway to forward to your other web based services
## Quick Setup
1. Install Docker and Docker-Compose
- [Docker Install documentation](https://docs.docker.com/install/)
- [Docker-Compose Install documentation](https://docs.docker.com/compose/install/)
2. Create a docker-compose.yml file similar to this:
```yml
version: '3'
services:
app:
image: 'jc21/nginx-proxy-manager:latest'
restart: unless-stopped
ports:
- '80:80'
- '81:81'
- '443:443'
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
```
3. Bring up your stack by running
```bash
docker-compose up -d
```
4. Log in to the Admin UI
When your docker container is running, connect to it on port `81` for the admin interface.
Sometimes this can take a little bit because of the entropy of keys.
[http://127.0.0.1:81](http://127.0.0.1:81)
Default Admin User:
```
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.
## Contributors
Special thanks to the following contributors:
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tr>
<td align="center">
<a href="https://github.com/Subv">
<img src="https://avatars1.githubusercontent.com/u/357072?s=460&u=d8adcdc91d749ae53e177973ed9b6bb6c4c894a3&v=4" width="80" alt=""/>
<br /><sub><b>Sebastian Valle</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Indemnity83">
<img src="https://avatars3.githubusercontent.com/u/35218?s=460&u=7082004ff35138157c868d7d9c683ccebfce5968&v=4" width="80" alt=""/>
<br /><sub><b>Kyle Klaus</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/theraw">
<img src="https://avatars1.githubusercontent.com/u/32969774?s=460&u=6b359971e15685fb0359e6a8c065a399b40dc228&v=4" width="80" alt=""/>
<br /><sub><b>ƬHE ЯAW</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/spalger">
<img src="https://avatars2.githubusercontent.com/u/1329312?s=400&u=565223e38f1c052afb4c5dcca3fcf1c63ba17ae7&v=4" width="80" alt=""/>
<br /><sub><b>Spencer</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Xantios">
<img src="https://avatars3.githubusercontent.com/u/1507836?s=460&v=4" width="80" alt=""/>
<br /><sub><b>Xantios Krugor</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/dpanesso">
<img src="https://avatars2.githubusercontent.com/u/2687121?s=460&v=4" width="80" alt=""/>
<br /><sub><b>David Panesso</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/IronTooch">
<img src="https://avatars3.githubusercontent.com/u/27360514?s=460&u=69bf854a6647c55725f62ecb8d39249c6c0b2602&v=4" width="80" alt=""/>
<br /><sub><b>IronTooch</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/damianog">
<img src="https://avatars1.githubusercontent.com/u/2786682?s=460&u=76c6136fae797abb76b951cd8a246dcaecaf21af&v=4" width="80" alt=""/>
<br /><sub><b>Damiano</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/tfmm">
<img src="https://avatars3.githubusercontent.com/u/6880538?s=460&u=ce0160821cc4aa802df8395200f2d4956a5bc541&v=4" width="80" alt=""/>
<br /><sub><b>Russ</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/margaale">
<img src="https://avatars3.githubusercontent.com/u/20794934?s=460&v=4" width="80" alt=""/>
<br /><sub><b>Marcelo Castagna</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Steven-Harris">
<img src="https://avatars2.githubusercontent.com/u/7720242?s=460&v=4" width="80" alt=""/>
<br /><sub><b>Steven Harris</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/jlesage">
<img src="https://avatars0.githubusercontent.com/u/1791123?s=460&v=4" width="80" alt=""/>
<br /><sub><b>Jocelyn Le Sage</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/cmer">
<img src="https://avatars0.githubusercontent.com/u/412?s=460&u=67dd8b2e3661bfd6f68ec1eaa5b9821bd8a321cd&v=4" width="80" alt=""/>
<br /><sub><b>Carl Mercier</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/the1ts">
<img src="https://avatars1.githubusercontent.com/u/84956?s=460&v=4" width="80" alt=""/>
<br /><sub><b>Paul Mansfield</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/OhHeyAlan">
<img src="https://avatars0.githubusercontent.com/u/11955126?s=460&u=fbaa5a1a4f73ef8960132c703349bfd037fe2630&v=4" width="80" alt=""/>
<br /><sub><b>OhHeyAlan</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/dogmatic69">
<img src="https://avatars2.githubusercontent.com/u/94674?s=460&u=ca7647de53145c6283b6373ade5dc94ba99347db&v=4" width="80" alt=""/>
<br /><sub><b>Carl Sutton</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/tg44">
<img src="https://avatars0.githubusercontent.com/u/31839?s=460&u=ad32f4cadfef5e5fb09cdfa4b7b7b36a99ba6811&v=4" width="80" alt=""/>
<br /><sub><b>Gergő Törcsvári</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/vrenjith">
<img src="https://avatars3.githubusercontent.com/u/2093241?s=460&u=96ce93a9bebabdd0a60a2dc96cd093a41d5edaba&v=4" width="80" alt=""/>
<br /><sub><b>vrenjith</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/duhruh">
<img src="https://avatars2.githubusercontent.com/u/1133969?s=460&u=c0691e6131ec6d516416c1c6fcedb5034f877bbe&v=4" width="80" alt=""/>
<br /><sub><b>David Rivera</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/jipjan">
<img src="https://avatars2.githubusercontent.com/u/1384618?s=460&v=4" width="80" alt=""/>
<br /><sub><b>Jaap-Jan de Wit</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/jmwebslave">
<img src="https://avatars2.githubusercontent.com/u/6118262?s=460&u=7db409c47135b1e141c366bbb03ed9fae6ac2638&v=4" width="80" alt=""/>
<br /><sub><b>James Morgan</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/chaptergy">
<img src="https://avatars2.githubusercontent.com/u/26956711?s=460&u=7d9adebabb6b4e7af7cb05d98d751087a372304b&v=4" width="80" alt=""/>
<br /><sub><b>chaptergy</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Philip-Mooney">
<img src="https://avatars0.githubusercontent.com/u/48624631?s=460&v=4" width="80" alt=""/>
<br /><sub><b>Philip Mooney</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/WaterCalm">
<img src="https://avatars1.githubusercontent.com/u/23502129?s=400&v=4" width="80" alt=""/>
<br /><sub><b>WaterCalm</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/lebrou34">
<img src="https://avatars1.githubusercontent.com/u/16373103?s=460&v=4" width="80" alt=""/>
<br /><sub><b>lebrou34</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/lightglitch">
<img src="https://avatars0.githubusercontent.com/u/196953?s=460&v=4" width="80" alt=""/>
<br /><sub><b>Mário Franco</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/klutchell">
<img src="https://avatars3.githubusercontent.com/u/20458272?s=460&v=4" width="80" alt=""/>
<br /><sub><b>Kyle Harding</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/ahgraber">
<img src="https://avatars.githubusercontent.com/u/24922003?s=460&u=8376c9f00af9b6057ba4d2fb03b4f1b20a75277f&v=4" width="80" alt=""/>
<br /><sub><b>Alex Graber</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/MooBaloo">
<img src="https://avatars.githubusercontent.com/u/9493496?s=460&v=4" width="80" alt=""/>
<br /><sub><b>MooBaloo</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Shuro">
<img src="https://avatars.githubusercontent.com/u/944030?s=460&v=4" width="80" alt=""/>
<br /><sub><b>Shuro</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/lorisbergeron">
<img src="https://avatars.githubusercontent.com/u/51918567?s=460&u=778e4ff284b7d7304450f98421c99f79298371fb&v=4" width="80" alt=""/>
<br /><sub><b>Loris Bergeron</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/hepelayo">
<img src="https://avatars.githubusercontent.com/u/8243119?v=4" width="80" alt=""/>
<br /><sub><b>hepelayo</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/jonasled">
<img src="https://avatars.githubusercontent.com/u/46790650?v=4" width="80" alt=""/>
<br /><sub><b>Jonas Leder</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/stegmannb">
<img src="https://avatars.githubusercontent.com/u/12850482?v=4" width="80" alt=""/>
<br /><sub><b>Bastian Stegmann</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Stealthii">
<img src="https://avatars.githubusercontent.com/u/998920?v=4" width="80" alt=""/>
<br /><sub><b>Stealthii</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/thegamingninja">
<img src="https://avatars.githubusercontent.com/u/8020534?v=4" width="80" alt=""/>
<br /><sub><b>THEGamingninja</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/italobb">
<img src="https://avatars.githubusercontent.com/u/1801687?v=4" width="80" alt=""/>
<br /><sub><b>Italo Borssatto</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/GurjinderSingh">
<img src="https://avatars.githubusercontent.com/u/3470709?v=4" width="80" alt=""/>
<br /><sub><b>Gurjinder Singh</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/phantomski77">
<img src="https://avatars.githubusercontent.com/u/69464125?v=4" width="80" alt=""/>
<br /><sub><b>David Dosoudil</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/ijaron">
<img src="https://avatars.githubusercontent.com/u/5156472?v=4" width="80" alt=""/>
<br /><sub><b>ijaron</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/nielscil">
<img src="https://avatars.githubusercontent.com/u/9073152?v=4" width="80" alt=""/>
<br /><sub><b>Niels Bouma</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/ogarai">
<img src="https://avatars.githubusercontent.com/u/2949572?v=4" width="80" alt=""/>
<br /><sub><b>Orko Garai</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/baruffaldi">
<img src="https://avatars.githubusercontent.com/u/36949?v=4" width="80" alt=""/>
<br /><sub><b>Filippo Baruffaldi</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/bikram990">
<img src="https://avatars.githubusercontent.com/u/6782131?v=4" width="80" alt=""/>
<br /><sub><b>Bikramjeet Singh</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/razvanstoica89">
<img src="https://avatars.githubusercontent.com/u/28236583?v=4" width="80" alt=""/>
<br /><sub><b>Razvan Stoica</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/psharma04">
<img src="https://avatars.githubusercontent.com/u/22587474?v=4" width="80" alt=""/>
<br /><sub><b>RBXII3</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/demize">
<img src="https://avatars.githubusercontent.com/u/264914?v=4" width="80" alt=""/>
<br /><sub><b>demize</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/PUP-Loki">
<img src="https://avatars.githubusercontent.com/u/75944209?v=4" width="80" alt=""/>
<br /><sub><b>PUP-Loki</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/DSorlov">
<img src="https://avatars.githubusercontent.com/u/8133650?v=4" width="80" alt=""/>
<br /><sub><b>Daniel Sörlöv</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/Theyooo">
<img src="https://avatars.githubusercontent.com/u/58510131?v=4" width="80" alt=""/>
<br /><sub><b>Theyooo</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/mrdink">
<img src="https://avatars.githubusercontent.com/u/514751?v=4" width="80" alt=""/>
<br /><sub><b>Justin Peacock</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/ChrisTracy">
<img src="https://avatars.githubusercontent.com/u/58871574?v=4" width="80" alt=""/>
<br /><sub><b>Chris Tracy</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Fuechslein">
<img src="https://avatars.githubusercontent.com/u/15112818?v=4" width="80" alt=""/>
<br /><sub><b>Fuechslein</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/nightah">
<img src="https://avatars.githubusercontent.com/u/3339418?v=4" width="80" alt=""/>
<br /><sub><b>Amir Zarrinkafsh</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/gabbe">
<img src="https://avatars.githubusercontent.com/u/156397?v=4" width="80" alt=""/>
<br /><sub><b>gabbe</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/bmbvenom">
<img src="https://avatars.githubusercontent.com/u/20530371?v=4" width="80" alt=""/>
<br /><sub><b>bmbvenom</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/FMeinicke">
<img src="https://avatars.githubusercontent.com/u/42121639?v=4" width="80" alt=""/>
<br /><sub><b>Florian Meinicke</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/ssrahul96">
<img src="https://avatars.githubusercontent.com/u/15570570?v=4" width="80" alt=""/>
<br /><sub><b>Rahul Somasundaram</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/BjoernAkAManf">
<img src="https://avatars.githubusercontent.com/u/833043?v=4" width="80" alt=""/>
<br /><sub><b>Björn Heinrichs</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/realJoshByrnes">
<img src="https://avatars.githubusercontent.com/u/204185?v=4" width="80" alt=""/>
<br /><sub><b>Josh Byrnes</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/bergi9">
<img src="https://avatars.githubusercontent.com/u/5556750?v=4" width="80" alt=""/>
<br /><sub><b>bergi9</b></sub>
</a>
</td>
</tr>
</table>
<!-- markdownlint-enable -->
<!-- prettier-ignore-end -->

73
backend/.eslintrc.json Normal file
View File

@ -0,0 +1,73 @@
{
"env": {
"node": true,
"es6": true
},
"extends": [
"eslint:recommended"
],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
},
"plugins": [
"align-assignments"
],
"rules": {
"arrow-parens": [
"error",
"always"
],
"indent": [
"error",
"tab"
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
],
"key-spacing": [
"error",
{
"align": "value"
}
],
"comma-spacing": [
"error",
{
"before": false,
"after": true
}
],
"func-call-spacing": [
"error",
"never"
],
"keyword-spacing": [
"error",
{
"before": true
}
],
"no-irregular-whitespace": "error",
"no-unused-expressions": 0,
"align-assignments/align-assignments": [
2,
{
"requiresOnly": false
}
]
}
}

8
backend/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
config/development.json
data/*
yarn-error.log
tmp
certbot.log
node_modules
core.*

11
backend/.prettierrc Normal file
View File

@ -0,0 +1,11 @@
{
"printWidth": 320,
"tabWidth": 4,
"useTabs": true,
"semi": true,
"singleQuote": true,
"bracketSpacing": true,
"jsxBracketSameLine": true,
"trailingComma": "all",
"proseWrap": "always"
}

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

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

89
backend/app.js Normal file
View File

@ -0,0 +1,89 @@
const express = require('express');
const bodyParser = require('body-parser');
const fileUpload = require('express-fileupload');
const compression = require('compression');
const log = require('./logger').express;
/**
* App
*/
const app = express();
app.use(fileUpload());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
// Gzip
app.use(compression());
/**
* General Logging, BEFORE routes
*/
app.disable('x-powered-by');
app.enable('trust proxy', ['loopback', 'linklocal', 'uniquelocal']);
app.enable('strict routing');
// pretty print JSON when not live
if (process.env.NODE_ENV !== 'production') {
app.set('json spaces', 2);
}
// CORS for everything
app.use(require('./lib/express/cors'));
// General security/cache related headers + server header
app.use(function (req, res, next) {
let x_frame_options = 'DENY';
if (typeof process.env.X_FRAME_OPTIONS !== 'undefined' && process.env.X_FRAME_OPTIONS) {
x_frame_options = process.env.X_FRAME_OPTIONS;
}
res.set({
'X-XSS-Protection': '1; mode=block',
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': x_frame_options,
'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
Pragma: 'no-cache',
Expires: 0
});
next();
});
app.use(require('./lib/express/jwt')());
app.use('/', require('./routes/api/main'));
// production error handler
// no stacktraces leaked to user
// eslint-disable-next-line
app.use(function (err, req, res, next) {
let payload = {
error: {
code: err.status,
message: err.public ? err.message : 'Internal Error'
}
};
if (process.env.NODE_ENV === 'development' || (req.baseUrl + req.path).includes('nginx/certificates')) {
payload.debug = {
stack: typeof err.stack !== 'undefined' && err.stack ? err.stack.split('\n') : null,
previous: err.previous
};
}
// Not every error is worth logging - but this is good for now until it gets annoying.
if (typeof err.stack !== 'undefined' && err.stack) {
if (process.env.NODE_ENV === 'development' || process.env.DEBUG) {
log.debug(err.stack);
} else if (typeof err.public == 'undefined' || !err.public) {
log.warn(err.message);
}
}
res
.status(err.status || 500)
.send(payload);
});
module.exports = app;

View File

@ -0,0 +1,26 @@
{
"database": {
"engine": "knex-native",
"knex": {
"client": "sqlite3",
"connection": {
"filename": "/app/config/mydb.sqlite"
},
"pool": {
"min": 0,
"max": 1,
"createTimeoutMillis": 3000,
"acquireTimeoutMillis": 30000,
"idleTimeoutMillis": 30000,
"reapIntervalMillis": 1000,
"createRetryIntervalMillis": 100,
"propagateCreateError": false
},
"migrations": {
"tableName": "migrations",
"stub": "src/backend/lib/migrate_template.js",
"directory": "src/backend/migrations"
}
}
}
}

33
backend/db.js Normal file
View File

@ -0,0 +1,33 @@
const config = require('config');
if (!config.has('database')) {
throw new Error('Database config does not exist! Please read the instructions: https://github.com/jc21/nginx-proxy-manager/blob/master/doc/INSTALL.md');
}
function generateDbConfig() {
if (config.database.engine === 'knex-native') {
return config.database.knex;
} else
return {
client: config.database.engine,
connection: {
host: config.database.host,
user: config.database.user,
password: config.database.password,
database: config.database.name,
port: config.database.port
},
migrations: {
tableName: 'migrations'
}
};
}
let data = generateDbConfig();
if (typeof config.database.version !== 'undefined') {
data.version = config.database.version;
}
module.exports = require('knex')(data);

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

File diff suppressed because it is too large Load Diff

135
backend/index.js Normal file
View File

@ -0,0 +1,135 @@
#!/usr/bin/env node
const logger = require('./logger').global;
async function appStart () {
// Create config file db settings if environment variables have been set
await createDbConfigFromEnvironment();
const migrate = require('./migrate');
const setup = require('./setup');
const app = require('./app');
const apiValidator = require('./lib/validator/api');
const internalCertificate = require('./internal/certificate');
const internalIpRanges = require('./internal/ip_ranges');
return migrate.latest()
.then(setup)
.then(() => {
return apiValidator.loadSchemas;
})
.then(internalIpRanges.fetch)
.then(() => {
internalCertificate.initTimer();
internalIpRanges.initTimer();
const server = app.listen(3000, () => {
logger.info('Backend PID ' + process.pid + ' listening on port 3000 ...');
process.on('SIGTERM', () => {
logger.info('PID ' + process.pid + ' received SIGTERM');
server.close(() => {
logger.info('Stopping.');
process.exit(0);
});
});
});
})
.catch((err) => {
logger.error(err.message);
setTimeout(appStart, 1000);
});
}
async function createDbConfigFromEnvironment() {
return new Promise((resolve, reject) => {
const envMysqlHost = process.env.DB_MYSQL_HOST || null;
const envMysqlPort = process.env.DB_MYSQL_PORT || null;
const envMysqlUser = process.env.DB_MYSQL_USER || null;
const envMysqlName = process.env.DB_MYSQL_NAME || null;
let envSqliteFile = process.env.DB_SQLITE_FILE || null;
const fs = require('fs');
const filename = (process.env.NODE_CONFIG_DIR || './config') + '/' + (process.env.NODE_ENV || 'default') + '.json';
let configData = {};
try {
configData = require(filename);
} catch (err) {
// do nothing
}
if (configData.database && configData.database.engine && !configData.database.fromEnv) {
logger.info('Manual db configuration already exists, skipping config creation from environment variables');
resolve();
return;
}
if ((!envMysqlHost || !envMysqlPort || !envMysqlUser || !envMysqlName) && !envSqliteFile){
envSqliteFile = '/data/database.sqlite';
logger.info(`No valid environment variables for database provided, using default SQLite file '${envSqliteFile}'`);
}
if (envMysqlHost && envMysqlPort && envMysqlUser && envMysqlName) {
const newConfig = {
fromEnv: true,
engine: 'mysql',
host: envMysqlHost,
port: envMysqlPort,
user: envMysqlUser,
password: process.env.DB_MYSQL_PASSWORD,
name: envMysqlName,
};
if (JSON.stringify(configData.database) === JSON.stringify(newConfig)) {
// Config is unchanged, skip overwrite
resolve();
return;
}
logger.info('Generating MySQL knex configuration from environment variables');
configData.database = newConfig;
} else {
const newConfig = {
fromEnv: true,
engine: 'knex-native',
knex: {
client: 'sqlite3',
connection: {
filename: envSqliteFile
},
useNullAsDefault: true
}
};
if (JSON.stringify(configData.database) === JSON.stringify(newConfig)) {
// Config is unchanged, skip overwrite
resolve();
return;
}
logger.info('Generating SQLite knex configuration');
configData.database = newConfig;
}
// Write config
fs.writeFile(filename, JSON.stringify(configData, null, 2), (err) => {
if (err) {
logger.error('Could not write db config to config file: ' + filename);
reject(err);
} else {
logger.debug('Wrote db configuration to config file: ' + filename);
resolve();
}
});
});
}
try {
appStart();
} catch (err) {
logger.error(err.message, err);
process.exit(1);
}

View File

@ -0,0 +1,534 @@
const _ = require('lodash');
const fs = require('fs');
const batchflow = require('batchflow');
const logger = require('../logger').access;
const error = require('../lib/error');
const accessListModel = require('../models/access_list');
const accessListAuthModel = require('../models/access_list_auth');
const accessListClientModel = require('../models/access_list_client');
const proxyHostModel = require('../models/proxy_host');
const internalAuditLog = require('./audit-log');
const internalNginx = require('./nginx');
const utils = require('../lib/utils');
function omissions () {
return ['is_deleted'];
}
const internalAccessList = {
/**
* @param {Access} access
* @param {Object} data
* @returns {Promise}
*/
create: (access, data) => {
return access.can('access_lists:create', data)
.then((/*access_data*/) => {
return accessListModel
.query()
.omit(omissions())
.insertAndFetch({
name: data.name,
satisfy_any: data.satisfy_any,
pass_auth: data.pass_auth,
owner_user_id: access.token.getUserId(1)
});
})
.then((row) => {
data.id = row.id;
let promises = [];
// Now add the items
data.items.map((item) => {
promises.push(accessListAuthModel
.query()
.insert({
access_list_id: row.id,
username: item.username,
password: item.password
})
);
});
// Now add the clients
if (typeof data.clients !== 'undefined' && data.clients) {
data.clients.map((client) => {
promises.push(accessListClientModel
.query()
.insert({
access_list_id: row.id,
address: client.address,
directive: client.directive
})
);
});
}
return Promise.all(promises);
})
.then(() => {
// re-fetch with expansions
return internalAccessList.get(access, {
id: data.id,
expand: ['owner', 'items', 'clients', 'proxy_hosts.access_list.[clients,items]']
}, true /* <- skip masking */);
})
.then((row) => {
// Audit log
data.meta = _.assign({}, data.meta || {}, row.meta);
return internalAccessList.build(row)
.then(() => {
if (row.proxy_host_count) {
return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
}
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'created',
object_type: 'access-list',
object_id: row.id,
meta: internalAccessList.maskItems(data)
});
})
.then(() => {
return internalAccessList.maskItems(row);
});
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Integer} data.id
* @param {String} [data.name]
* @param {String} [data.items]
* @return {Promise}
*/
update: (access, data) => {
return access.can('access_lists:update', data.id)
.then((/*access_data*/) => {
return internalAccessList.get(access, {id: data.id});
})
.then((row) => {
if (row.id !== data.id) {
// Sanity check that something crazy hasn't happened
throw new error.InternalValidationError('Access List could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
}
})
.then(() => {
// patch name if specified
if (typeof data.name !== 'undefined' && data.name) {
return accessListModel
.query()
.where({id: data.id})
.patch({
name: data.name,
satisfy_any: data.satisfy_any,
pass_auth: data.pass_auth,
});
}
})
.then(() => {
// Check for items and add/update/remove them
if (typeof data.items !== 'undefined' && data.items) {
let promises = [];
let items_to_keep = [];
data.items.map(function (item) {
if (item.password) {
promises.push(accessListAuthModel
.query()
.insert({
access_list_id: data.id,
username: item.username,
password: item.password
})
);
} else {
// This was supplied with an empty password, which means keep it but don't change the password
items_to_keep.push(item.username);
}
});
let query = accessListAuthModel
.query()
.delete()
.where('access_list_id', data.id);
if (items_to_keep.length) {
query.andWhere('username', 'NOT IN', items_to_keep);
}
return query
.then(() => {
// Add new items
if (promises.length) {
return Promise.all(promises);
}
});
}
})
.then(() => {
// Check for clients and add/update/remove them
if (typeof data.clients !== 'undefined' && data.clients) {
let promises = [];
data.clients.map(function (client) {
if (client.address) {
promises.push(accessListClientModel
.query()
.insert({
access_list_id: data.id,
address: client.address,
directive: client.directive
})
);
}
});
let query = accessListClientModel
.query()
.delete()
.where('access_list_id', data.id);
return query
.then(() => {
// Add new items
if (promises.length) {
return Promise.all(promises);
}
});
}
})
.then(internalNginx.reload)
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'updated',
object_type: 'access-list',
object_id: data.id,
meta: internalAccessList.maskItems(data)
});
})
.then(() => {
// re-fetch with expansions
return internalAccessList.get(access, {
id: data.id,
expand: ['owner', 'items', 'clients', 'proxy_hosts.access_list.[clients,items]']
}, true /* <- skip masking */);
})
.then((row) => {
return internalAccessList.build(row)
.then(() => {
if (row.proxy_host_count) {
return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
}
})
.then(() => {
return internalAccessList.maskItems(row);
});
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Integer} data.id
* @param {Array} [data.expand]
* @param {Array} [data.omit]
* @param {Boolean} [skip_masking]
* @return {Promise}
*/
get: (access, data, skip_masking) => {
if (typeof data === 'undefined') {
data = {};
}
return access.can('access_lists:get', data.id)
.then((access_data) => {
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')
.where('access_list.is_deleted', 0)
.andWhere('access_list.id', data.id)
.allowEager('[owner,items,clients,proxy_hosts.[*, access_list.[clients,items]]]')
.omit(['access_list.is_deleted'])
.first();
if (access_data.permission_visibility !== 'all') {
query.andWhere('access_list.owner_user_id', access.token.getUserId(1));
}
// Custom omissions
if (typeof data.omit !== 'undefined' && data.omit !== null) {
query.omit(data.omit);
}
if (typeof data.expand !== 'undefined' && data.expand !== null) {
query.eager('[' + data.expand.join(', ') + ']');
}
return query;
})
.then((row) => {
if (row) {
if (!skip_masking && typeof row.items !== 'undefined' && row.items) {
row = internalAccessList.maskItems(row);
}
return _.omit(row, omissions());
} else {
throw new error.ItemNotFoundError(data.id);
}
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Integer} data.id
* @param {String} [data.reason]
* @returns {Promise}
*/
delete: (access, data) => {
return access.can('access_lists:delete', data.id)
.then(() => {
return internalAccessList.get(access, {id: data.id, expand: ['proxy_hosts', 'items', 'clients']});
})
.then((row) => {
if (!row) {
throw new error.ItemNotFoundError(data.id);
}
// 1. update row to be deleted
// 2. update any proxy hosts that were using it (ignoring permissions)
// 3. reconfigure those hosts
// 4. audit log
// 1. update row to be deleted
return accessListModel
.query()
.where('id', row.id)
.patch({
is_deleted: 1
})
.then(() => {
// 2. update any proxy hosts that were using it (ignoring permissions)
if (row.proxy_hosts) {
return proxyHostModel
.query()
.where('access_list_id', '=', row.id)
.patch({access_list_id: 0})
.then(() => {
// 3. reconfigure those hosts, then reload nginx
// set the access_list_id to zero for these items
row.proxy_hosts.map(function (val, idx) {
row.proxy_hosts[idx].access_list_id = 0;
});
return internalNginx.bulkGenerateConfigs('proxy_host', row.proxy_hosts);
})
.then(() => {
return internalNginx.reload();
});
}
})
.then(() => {
// delete the htpasswd file
let htpasswd_file = internalAccessList.getFilename(row);
try {
fs.unlinkSync(htpasswd_file);
} catch (err) {
// do nothing
}
})
.then(() => {
// 4. audit log
return internalAuditLog.add(access, {
action: 'deleted',
object_type: 'access-list',
object_id: row.id,
meta: _.omit(internalAccessList.maskItems(row), ['is_deleted', 'proxy_hosts'])
});
});
})
.then(() => {
return true;
});
},
/**
* All Lists
*
* @param {Access} access
* @param {Array} [expand]
* @param {String} [search_query]
* @returns {Promise}
*/
getAll: (access, expand, search_query) => {
return access.can('access_lists:list')
.then((access_data) => {
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')
.where('access_list.is_deleted', 0)
.groupBy('access_list.id')
.omit(['access_list.is_deleted'])
.allowEager('[owner,items,clients]')
.orderBy('access_list.name', 'ASC');
if (access_data.permission_visibility !== 'all') {
query.andWhere('access_list.owner_user_id', access.token.getUserId(1));
}
// Query is used for searching
if (typeof search_query === 'string') {
query.where(function () {
this.where('name', 'like', '%' + search_query + '%');
});
}
if (typeof expand !== 'undefined' && expand !== null) {
query.eager('[' + expand.join(', ') + ']');
}
return query;
})
.then((rows) => {
if (rows) {
rows.map(function (row, idx) {
if (typeof row.items !== 'undefined' && row.items) {
rows[idx] = internalAccessList.maskItems(row);
}
});
}
return rows;
});
},
/**
* Report use
*
* @param {Integer} user_id
* @param {String} visibility
* @returns {Promise}
*/
getCount: (user_id, visibility) => {
let query = accessListModel
.query()
.count('id as count')
.where('is_deleted', 0);
if (visibility !== 'all') {
query.andWhere('owner_user_id', user_id);
}
return query.first()
.then((row) => {
return parseInt(row.count, 10);
});
},
/**
* @param {Object} list
* @returns {Object}
*/
maskItems: (list) => {
if (list && typeof list.items !== 'undefined') {
list.items.map(function (val, idx) {
let repeat_for = 8;
let first_char = '*';
if (typeof val.password !== 'undefined' && val.password) {
repeat_for = val.password.length - 1;
first_char = val.password.charAt(0);
}
list.items[idx].hint = first_char + ('*').repeat(repeat_for);
list.items[idx].password = '';
});
}
return list;
},
/**
* @param {Object} list
* @param {Integer} list.id
* @returns {String}
*/
getFilename: (list) => {
return '/data/access/' + list.id;
},
/**
* @param {Object} list
* @param {Integer} list.id
* @param {String} list.name
* @param {Array} list.items
* @returns {Promise}
*/
build: (list) => {
logger.info('Building Access file #' + list.id + ' for: ' + list.name);
return new Promise((resolve, reject) => {
let htpasswd_file = internalAccessList.getFilename(list);
// 1. remove any existing access file
try {
fs.unlinkSync(htpasswd_file);
} catch (err) {
// do nothing
}
// 2. create empty access file
try {
fs.writeFileSync(htpasswd_file, '', {encoding: 'utf8'});
resolve(htpasswd_file);
} catch (err) {
reject(err);
}
})
.then((htpasswd_file) => {
// 3. generate password for each user
if (list.items.length) {
return new Promise((resolve, reject) => {
batchflow(list.items).sequential()
.each((i, item, next) => {
if (typeof item.password !== 'undefined' && item.password.length) {
logger.info('Adding: ' + item.username);
utils.exec('/usr/bin/htpasswd -b "' + htpasswd_file + '" "' + item.username + '" "' + item.password + '"')
.then((/*result*/) => {
next();
})
.catch((err) => {
logger.error(err);
next(err);
});
}
})
.error((err) => {
logger.error(err);
reject(err);
})
.end((results) => {
logger.success('Built Access file #' + list.id + ' for: ' + list.name);
resolve(results);
});
});
}
});
}
};
module.exports = internalAccessList;

View File

@ -0,0 +1,78 @@
const error = require('../lib/error');
const auditLogModel = require('../models/audit-log');
const internalAuditLog = {
/**
* All logs
*
* @param {Access} access
* @param {Array} [expand]
* @param {String} [search_query]
* @returns {Promise}
*/
getAll: (access, expand, search_query) => {
return access.can('auditlog:list')
.then(() => {
let query = auditLogModel
.query()
.orderBy('created_on', 'DESC')
.orderBy('id', 'DESC')
.limit(100)
.allowEager('[user]');
// Query is used for searching
if (typeof search_query === 'string') {
query.where(function () {
this.where('meta', 'like', '%' + search_query + '%');
});
}
if (typeof expand !== 'undefined' && expand !== null) {
query.eager('[' + expand.join(', ') + ']');
}
return query;
});
},
/**
* This method should not be publicly used, it doesn't check certain things. It will be assumed
* that permission to add to audit log is already considered, however the access token is used for
* default user id determination.
*
* @param {Access} access
* @param {Object} data
* @param {String} data.action
* @param {Number} [data.user_id]
* @param {Number} [data.object_id]
* @param {Number} [data.object_type]
* @param {Object} [data.meta]
* @returns {Promise}
*/
add: (access, data) => {
return new Promise((resolve, reject) => {
// Default the user id
if (typeof data.user_id === 'undefined' || !data.user_id) {
data.user_id = access.token.getUserId(1);
}
if (typeof data.action === 'undefined' || !data.action) {
reject(new error.InternalValidationError('Audit log entry must contain an Action'));
} else {
// Make sure at least 1 of the IDs are set and action
resolve(auditLogModel
.query()
.insert({
user_id: data.user_id,
action: data.action,
object_type: data.object_type || '',
object_id: data.object_id || 0,
meta: data.meta || {}
}));
}
});
}
};
module.exports = internalAuditLog;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,461 @@
const _ = require('lodash');
const error = require('../lib/error');
const deadHostModel = require('../models/dead_host');
const internalHost = require('./host');
const internalNginx = require('./nginx');
const internalAuditLog = require('./audit-log');
const internalCertificate = require('./certificate');
function omissions () {
return ['is_deleted'];
}
const internalDeadHost = {
/**
* @param {Access} access
* @param {Object} data
* @returns {Promise}
*/
create: (access, data) => {
let create_certificate = data.certificate_id === 'new';
if (create_certificate) {
delete data.certificate_id;
}
return access.can('dead_hosts:create', data)
.then((/*access_data*/) => {
// Get a list of the domain names and check each of them against existing records
let domain_name_check_promises = [];
data.domain_names.map(function (domain_name) {
domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name));
});
return Promise.all(domain_name_check_promises)
.then((check_results) => {
check_results.map(function (result) {
if (result.is_taken) {
throw new error.ValidationError(result.hostname + ' is already in use');
}
});
});
})
.then(() => {
// At this point the domains should have been checked
data.owner_user_id = access.token.getUserId(1);
data = internalHost.cleanSslHstsData(data);
return deadHostModel
.query()
.omit(omissions())
.insertAndFetch(data);
})
.then((row) => {
if (create_certificate) {
return internalCertificate.createQuickCertificate(access, data)
.then((cert) => {
// update host with cert id
return internalDeadHost.update(access, {
id: row.id,
certificate_id: cert.id
});
})
.then(() => {
return row;
});
} else {
return row;
}
})
.then((row) => {
// re-fetch with cert
return internalDeadHost.get(access, {
id: row.id,
expand: ['certificate', 'owner']
});
})
.then((row) => {
// Configure nginx
return internalNginx.configure(deadHostModel, 'dead_host', row)
.then(() => {
return row;
});
})
.then((row) => {
data.meta = _.assign({}, data.meta || {}, row.meta);
// Add to audit log
return internalAuditLog.add(access, {
action: 'created',
object_type: 'dead-host',
object_id: row.id,
meta: data
})
.then(() => {
return row;
});
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Number} data.id
* @return {Promise}
*/
update: (access, data) => {
let create_certificate = data.certificate_id === 'new';
if (create_certificate) {
delete data.certificate_id;
}
return access.can('dead_hosts:update', data.id)
.then((/*access_data*/) => {
// Get a list of the domain names and check each of them against existing records
let domain_name_check_promises = [];
if (typeof data.domain_names !== 'undefined') {
data.domain_names.map(function (domain_name) {
domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, 'dead', data.id));
});
return Promise.all(domain_name_check_promises)
.then((check_results) => {
check_results.map(function (result) {
if (result.is_taken) {
throw new error.ValidationError(result.hostname + ' is already in use');
}
});
});
}
})
.then(() => {
return internalDeadHost.get(access, {id: data.id});
})
.then((row) => {
if (row.id !== data.id) {
// Sanity check that something crazy hasn't happened
throw new error.InternalValidationError('404 Host 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);
data = internalHost.cleanSslHstsData(data, row);
return deadHostModel
.query()
.where({id: data.id})
.patch(data)
.then((saved_row) => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'updated',
object_type: 'dead-host',
object_id: row.id,
meta: data
})
.then(() => {
return _.omit(saved_row, omissions());
});
});
})
.then(() => {
return internalDeadHost.get(access, {
id: data.id,
expand: ['owner', 'certificate']
})
.then((row) => {
// Configure nginx
return internalNginx.configure(deadHostModel, 'dead_host', row)
.then((new_meta) => {
row.meta = new_meta;
row = internalHost.cleanRowCertificateMeta(row);
return _.omit(row, omissions());
});
});
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Number} data.id
* @param {Array} [data.expand]
* @param {Array} [data.omit]
* @return {Promise}
*/
get: (access, data) => {
if (typeof data === 'undefined') {
data = {};
}
return access.can('dead_hosts:get', data.id)
.then((access_data) => {
let query = deadHostModel
.query()
.where('is_deleted', 0)
.andWhere('id', data.id)
.allowEager('[owner,certificate]')
.first();
if (access_data.permission_visibility !== 'all') {
query.andWhere('owner_user_id', access.token.getUserId(1));
}
// Custom omissions
if (typeof data.omit !== 'undefined' && data.omit !== null) {
query.omit(data.omit);
}
if (typeof data.expand !== 'undefined' && data.expand !== null) {
query.eager('[' + data.expand.join(', ') + ']');
}
return query;
})
.then((row) => {
if (row) {
row = internalHost.cleanRowCertificateMeta(row);
return _.omit(row, omissions());
} else {
throw new error.ItemNotFoundError(data.id);
}
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Number} data.id
* @param {String} [data.reason]
* @returns {Promise}
*/
delete: (access, data) => {
return access.can('dead_hosts:delete', data.id)
.then(() => {
return internalDeadHost.get(access, {id: data.id});
})
.then((row) => {
if (!row) {
throw new error.ItemNotFoundError(data.id);
}
return deadHostModel
.query()
.where('id', row.id)
.patch({
is_deleted: 1
})
.then(() => {
// Delete Nginx Config
return internalNginx.deleteConfig('dead_host', row)
.then(() => {
return internalNginx.reload();
});
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'deleted',
object_type: 'dead-host',
object_id: row.id,
meta: _.omit(row, omissions())
});
});
})
.then(() => {
return true;
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Number} data.id
* @param {String} [data.reason]
* @returns {Promise}
*/
enable: (access, data) => {
return access.can('dead_hosts:update', data.id)
.then(() => {
return internalDeadHost.get(access, {
id: data.id,
expand: ['certificate', 'owner']
});
})
.then((row) => {
if (!row) {
throw new error.ItemNotFoundError(data.id);
} else if (row.enabled) {
throw new error.ValidationError('Host is already enabled');
}
row.enabled = 1;
return deadHostModel
.query()
.where('id', row.id)
.patch({
enabled: 1
})
.then(() => {
// Configure nginx
return internalNginx.configure(deadHostModel, 'dead_host', row);
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'enabled',
object_type: 'dead-host',
object_id: row.id,
meta: _.omit(row, omissions())
});
});
})
.then(() => {
return true;
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Number} data.id
* @param {String} [data.reason]
* @returns {Promise}
*/
disable: (access, data) => {
return access.can('dead_hosts:update', data.id)
.then(() => {
return internalDeadHost.get(access, {id: data.id});
})
.then((row) => {
if (!row) {
throw new error.ItemNotFoundError(data.id);
} else if (!row.enabled) {
throw new error.ValidationError('Host is already disabled');
}
row.enabled = 0;
return deadHostModel
.query()
.where('id', row.id)
.patch({
enabled: 0
})
.then(() => {
// Delete Nginx Config
return internalNginx.deleteConfig('dead_host', row)
.then(() => {
return internalNginx.reload();
});
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'disabled',
object_type: 'dead-host',
object_id: row.id,
meta: _.omit(row, omissions())
});
});
})
.then(() => {
return true;
});
},
/**
* All Hosts
*
* @param {Access} access
* @param {Array} [expand]
* @param {String} [search_query]
* @returns {Promise}
*/
getAll: (access, expand, search_query) => {
return access.can('dead_hosts:list')
.then((access_data) => {
let query = deadHostModel
.query()
.where('is_deleted', 0)
.groupBy('id')
.omit(['is_deleted'])
.allowEager('[owner,certificate]')
.orderBy('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') {
query.where(function () {
this.where('domain_names', 'like', '%' + search_query + '%');
});
}
if (typeof expand !== 'undefined' && expand !== null) {
query.eager('[' + expand.join(', ') + ']');
}
return query;
})
.then((rows) => {
if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) {
return internalHost.cleanAllRowsCertificateMeta(rows);
}
return rows;
});
},
/**
* Report use
*
* @param {Number} user_id
* @param {String} visibility
* @returns {Promise}
*/
getCount: (user_id, visibility) => {
let query = deadHostModel
.query()
.count('id as count')
.where('is_deleted', 0);
if (visibility !== 'all') {
query.andWhere('owner_user_id', user_id);
}
return query.first()
.then((row) => {
return parseInt(row.count, 10);
});
}
};
module.exports = internalDeadHost;

235
backend/internal/host.js Normal file
View File

@ -0,0 +1,235 @@
const _ = require('lodash');
const proxyHostModel = require('../models/proxy_host');
const redirectionHostModel = require('../models/redirection_host');
const deadHostModel = require('../models/dead_host');
const internalHost = {
/**
* Makes sure that the ssl_* and hsts_* fields play nicely together.
* ie: if there is no cert, then force_ssl is off.
* if force_ssl is off, then hsts_enabled is definitely off.
*
* @param {object} data
* @param {object} [existing_data]
* @returns {object}
*/
cleanSslHstsData: function (data, existing_data) {
existing_data = existing_data === undefined ? {} : existing_data;
let combined_data = _.assign({}, existing_data, data);
if (!combined_data.certificate_id) {
combined_data.ssl_forced = false;
combined_data.http2_support = false;
}
if (!combined_data.ssl_forced) {
combined_data.hsts_enabled = false;
}
if (!combined_data.hsts_enabled) {
combined_data.hsts_subdomains = false;
}
return combined_data;
},
/**
* used by the getAll functions of hosts, this removes the certificate meta if present
*
* @param {Array} rows
* @returns {Array}
*/
cleanAllRowsCertificateMeta: function (rows) {
rows.map(function (row, idx) {
if (typeof rows[idx].certificate !== 'undefined' && rows[idx].certificate) {
rows[idx].certificate.meta = {};
}
});
return rows;
},
/**
* used by the get/update functions of hosts, this removes the certificate meta if present
*
* @param {Object} row
* @returns {Object}
*/
cleanRowCertificateMeta: function (row) {
if (typeof row.certificate !== 'undefined' && row.certificate) {
row.certificate.meta = {};
}
return row;
},
/**
* This returns all the host types with any domain listed in the provided domain_names array.
* This is used by the certificates to temporarily disable any host that is using the domain
*
* @param {Array} domain_names
* @returns {Promise}
*/
getHostsWithDomains: function (domain_names) {
let promises = [
proxyHostModel
.query()
.where('is_deleted', 0),
redirectionHostModel
.query()
.where('is_deleted', 0),
deadHostModel
.query()
.where('is_deleted', 0)
];
return Promise.all(promises)
.then((promises_results) => {
let response_object = {
total_count: 0,
dead_hosts: [],
proxy_hosts: [],
redirection_hosts: []
};
if (promises_results[0]) {
// Proxy Hosts
response_object.proxy_hosts = internalHost._getHostsWithDomains(promises_results[0], domain_names);
response_object.total_count += response_object.proxy_hosts.length;
}
if (promises_results[1]) {
// Redirection Hosts
response_object.redirection_hosts = internalHost._getHostsWithDomains(promises_results[1], domain_names);
response_object.total_count += response_object.redirection_hosts.length;
}
if (promises_results[2]) {
// Dead Hosts
response_object.dead_hosts = internalHost._getHostsWithDomains(promises_results[2], domain_names);
response_object.total_count += response_object.dead_hosts.length;
}
return response_object;
});
},
/**
* Internal use only, checks to see if the domain is already taken by any other record
*
* @param {String} hostname
* @param {String} [ignore_type] 'proxy', 'redirection', 'dead'
* @param {Integer} [ignore_id] Must be supplied if type was also supplied
* @returns {Promise}
*/
isHostnameTaken: function (hostname, ignore_type, ignore_id) {
let promises = [
proxyHostModel
.query()
.where('is_deleted', 0)
.andWhere('domain_names', 'like', '%' + hostname + '%'),
redirectionHostModel
.query()
.where('is_deleted', 0)
.andWhere('domain_names', 'like', '%' + hostname + '%'),
deadHostModel
.query()
.where('is_deleted', 0)
.andWhere('domain_names', 'like', '%' + hostname + '%')
];
return Promise.all(promises)
.then((promises_results) => {
let is_taken = false;
if (promises_results[0]) {
// Proxy Hosts
if (internalHost._checkHostnameRecordsTaken(hostname, promises_results[0], ignore_type === 'proxy' && ignore_id ? ignore_id : 0)) {
is_taken = true;
}
}
if (promises_results[1]) {
// Redirection Hosts
if (internalHost._checkHostnameRecordsTaken(hostname, promises_results[1], ignore_type === 'redirection' && ignore_id ? ignore_id : 0)) {
is_taken = true;
}
}
if (promises_results[2]) {
// Dead Hosts
if (internalHost._checkHostnameRecordsTaken(hostname, promises_results[2], ignore_type === 'dead' && ignore_id ? ignore_id : 0)) {
is_taken = true;
}
}
return {
hostname: hostname,
is_taken: is_taken
};
});
},
/**
* Private call only
*
* @param {String} hostname
* @param {Array} existing_rows
* @param {Integer} [ignore_id]
* @returns {Boolean}
*/
_checkHostnameRecordsTaken: function (hostname, existing_rows, ignore_id) {
let is_taken = false;
if (existing_rows && existing_rows.length) {
existing_rows.map(function (existing_row) {
existing_row.domain_names.map(function (existing_hostname) {
// Does this domain match?
if (existing_hostname.toLowerCase() === hostname.toLowerCase()) {
if (!ignore_id || ignore_id !== existing_row.id) {
is_taken = true;
}
}
});
});
}
return is_taken;
},
/**
* Private call only
*
* @param {Array} hosts
* @param {Array} domain_names
* @returns {Array}
*/
_getHostsWithDomains: function (hosts, domain_names) {
let response = [];
if (hosts && hosts.length) {
hosts.map(function (host) {
let host_matches = false;
domain_names.map(function (domain_name) {
host.domain_names.map(function (host_domain_name) {
if (domain_name.toLowerCase() === host_domain_name.toLowerCase()) {
host_matches = true;
}
});
});
if (host_matches) {
response.push(host);
}
});
}
return response;
}
};
module.exports = internalHost;

View File

@ -0,0 +1,147 @@
const https = require('https');
const fs = require('fs');
const logger = require('../logger').ip_ranges;
const error = require('../lib/error');
const internalNginx = require('./nginx');
const { Liquid } = require('liquidjs');
const CLOUDFRONT_URL = 'https://ip-ranges.amazonaws.com/ip-ranges.json';
const CLOUDFARE_V4_URL = 'https://www.cloudflare.com/ips-v4';
const CLOUDFARE_V6_URL = 'https://www.cloudflare.com/ips-v6';
const internalIpRanges = {
interval_timeout: 1000 * 60 * 60 * 6, // 6 hours
interval: null,
interval_processing: false,
iteration_count: 0,
initTimer: () => {
logger.info('IP Ranges Renewal Timer initialized');
internalIpRanges.interval = setInterval(internalIpRanges.fetch, internalIpRanges.interval_timeout);
},
fetchUrl: (url) => {
return new Promise((resolve, reject) => {
logger.info('Fetching ' + url);
return https.get(url, (res) => {
res.setEncoding('utf8');
let raw_data = '';
res.on('data', (chunk) => {
raw_data += chunk;
});
res.on('end', () => {
resolve(raw_data);
});
}).on('error', (err) => {
reject(err);
});
});
},
/**
* Triggered at startup and then later by a timer, this will fetch the ip ranges from services and apply them to nginx.
*/
fetch: () => {
if (!internalIpRanges.interval_processing) {
internalIpRanges.interval_processing = true;
logger.info('Fetching IP Ranges from online services...');
let ip_ranges = [];
return internalIpRanges.fetchUrl(CLOUDFRONT_URL)
.then((cloudfront_data) => {
let data = JSON.parse(cloudfront_data);
if (data && typeof data.prefixes !== 'undefined') {
data.prefixes.map((item) => {
if (item.service === 'CLOUDFRONT') {
ip_ranges.push(item.ip_prefix);
}
});
}
if (data && typeof data.ipv6_prefixes !== 'undefined') {
data.ipv6_prefixes.map((item) => {
if (item.service === 'CLOUDFRONT') {
ip_ranges.push(item.ipv6_prefix);
}
});
}
})
.then(() => {
return internalIpRanges.fetchUrl(CLOUDFARE_V4_URL);
})
.then((cloudfare_data) => {
let items = cloudfare_data.split('\n');
ip_ranges = [... ip_ranges, ... items];
})
.then(() => {
return internalIpRanges.fetchUrl(CLOUDFARE_V6_URL);
})
.then((cloudfare_data) => {
let items = cloudfare_data.split('\n');
ip_ranges = [... ip_ranges, ... items];
})
.then(() => {
let clean_ip_ranges = [];
ip_ranges.map((range) => {
if (range) {
clean_ip_ranges.push(range);
}
});
return internalIpRanges.generateConfig(clean_ip_ranges)
.then(() => {
if (internalIpRanges.iteration_count) {
// Reload nginx
return internalNginx.reload();
}
});
})
.then(() => {
internalIpRanges.interval_processing = false;
internalIpRanges.iteration_count++;
})
.catch((err) => {
logger.error(err.message);
internalIpRanges.interval_processing = false;
});
}
},
/**
* @param {Array} ip_ranges
* @returns {Promise}
*/
generateConfig: (ip_ranges) => {
let renderEngine = new Liquid({
root: __dirname + '/../templates/'
});
return new Promise((resolve, reject) => {
let template = null;
let filename = '/etc/nginx/conf.d/include/ip_ranges.conf';
try {
template = fs.readFileSync(__dirname + '/../templates/ip_ranges.conf', {encoding: 'utf8'});
} catch (err) {
reject(new error.ConfigurationError(err.message));
return;
}
renderEngine
.parseAndRender(template, {ip_ranges: ip_ranges})
.then((config_text) => {
fs.writeFileSync(filename, config_text, {encoding: 'utf8'});
resolve(true);
})
.catch((err) => {
logger.warn('Could not write ' + filename + ':', err.message);
reject(new error.ConfigurationError(err.message));
});
});
}
};
module.exports = internalIpRanges;

435
backend/internal/nginx.js Normal file
View File

@ -0,0 +1,435 @@
const _ = require('lodash');
const fs = require('fs');
const logger = require('../logger').nginx;
const utils = require('../lib/utils');
const error = require('../lib/error');
const { Liquid } = require('liquidjs');
const debug_mode = process.env.NODE_ENV !== 'production' || !!process.env.DEBUG;
const internalNginx = {
/**
* This will:
* - test the nginx config first to make sure it's OK
* - create / recreate the config for the host
* - test again
* - IF OK: update the meta with online status
* - IF BAD: update the meta with offline status and remove the config entirely
* - then reload nginx
*
* @param {Object|String} model
* @param {String} host_type
* @param {Object} host
* @returns {Promise}
*/
configure: (model, host_type, host) => {
let combined_meta = {};
return internalNginx.test()
.then(() => {
// Nginx is OK
// We're deleting this config regardless.
return internalNginx.deleteConfig(host_type, host); // Don't throw errors, as the file may not exist at all
})
.then(() => {
return internalNginx.generateConfig(host_type, host);
})
.then(() => {
// Test nginx again and update meta with result
return internalNginx.test()
.then(() => {
// nginx is ok
combined_meta = _.assign({}, host.meta, {
nginx_online: true,
nginx_err: null
});
return model
.query()
.where('id', host.id)
.patch({
meta: combined_meta
});
})
.catch((err) => {
// Remove the error_log line because it's a docker-ism false positive that doesn't need to be reported.
// It will always look like this:
// nginx: [alert] could not open error log file: open() "/var/log/nginx/error.log" failed (6: No such device or address)
let valid_lines = [];
let err_lines = err.message.split('\n');
err_lines.map(function (line) {
if (line.indexOf('/var/log/nginx/error.log') === -1) {
valid_lines.push(line);
}
});
if (debug_mode) {
logger.error('Nginx test failed:', valid_lines.join('\n'));
}
// config is bad, update meta and delete config
combined_meta = _.assign({}, host.meta, {
nginx_online: false,
nginx_err: valid_lines.join('\n')
});
return model
.query()
.where('id', host.id)
.patch({
meta: combined_meta
})
.then(() => {
return internalNginx.deleteConfig(host_type, host, true);
});
});
})
.then(() => {
return internalNginx.reload();
})
.then(() => {
return combined_meta;
});
},
/**
* @returns {Promise}
*/
test: () => {
if (debug_mode) {
logger.info('Testing Nginx configuration');
}
return utils.exec('/usr/sbin/nginx -t -g "error_log off;"');
},
/**
* @returns {Promise}
*/
reload: () => {
return internalNginx.test()
.then(() => {
logger.info('Reloading Nginx');
return utils.exec('/usr/sbin/nginx -s reload');
});
},
/**
* @param {String} host_type
* @param {Integer} host_id
* @returns {String}
*/
getConfigName: (host_type, host_id) => {
host_type = host_type.replace(new RegExp('-', 'g'), '_');
if (host_type === 'default') {
return '/data/nginx/default_host/site.conf';
}
return '/data/nginx/' + host_type + '/' + host_id + '.conf';
},
/**
* Generates custom locations
* @param {Object} host
* @returns {Promise}
*/
renderLocations: (host) => {
//logger.info('host = ' + JSON.stringify(host, null, 2));
return new Promise((resolve, reject) => {
let template;
try {
template = fs.readFileSync(__dirname + '/../templates/_location.conf', {encoding: 'utf8'});
} catch (err) {
reject(new error.ConfigurationError(err.message));
return;
}
let renderer = new Liquid({
root: __dirname + '/../templates/'
});
let renderedLocations = '';
const locationRendering = async () => {
for (let i = 0; i < host.locations.length; i++) {
let locationCopy = Object.assign({}, {access_list_id: host.access_list_id}, {certificate_id: host.certificate_id},
{ssl_forced: host.ssl_forced}, {caching_enabled: host.caching_enabled}, {block_exploits: host.block_exploits},
{allow_websocket_upgrade: host.allow_websocket_upgrade}, {http2_support: host.http2_support},
{hsts_enabled: host.hsts_enabled}, {hsts_subdomains: host.hsts_subdomains}, {access_list: host.access_list},
{certificate: host.certificate}, host.locations[i]);
if (locationCopy.forward_host.indexOf('/') > -1) {
const splitted = locationCopy.forward_host.split('/');
locationCopy.forward_host = splitted.shift();
locationCopy.forward_path = `/${splitted.join('/')}`;
}
//logger.info('locationCopy = ' + JSON.stringify(locationCopy, null, 2));
// eslint-disable-next-line
renderedLocations += await renderer.parseAndRender(template, locationCopy);
}
};
locationRendering().then(() => resolve(renderedLocations));
});
},
/**
* @param {String} host_type
* @param {Object} host
* @returns {Promise}
*/
generateConfig: (host_type, host) => {
host_type = host_type.replace(new RegExp('-', 'g'), '_');
if (debug_mode) {
logger.info('Generating ' + host_type + ' Config:', host);
}
// logger.info('host = ' + JSON.stringify(host, null, 2));
let renderEngine = new Liquid({
root: __dirname + '/../templates/'
});
return new Promise((resolve, reject) => {
let template = null;
let filename = internalNginx.getConfigName(host_type, host.id);
try {
template = fs.readFileSync(__dirname + '/../templates/' + host_type + '.conf', {encoding: 'utf8'});
} catch (err) {
reject(new error.ConfigurationError(err.message));
return;
}
let locationsPromise;
let origLocations;
// Manipulate the data a bit before sending it to the template
if (host_type !== 'default') {
host.use_default_location = true;
if (typeof host.advanced_config !== 'undefined' && host.advanced_config) {
host.use_default_location = !internalNginx.advancedConfigHasDefaultLocation(host.advanced_config);
}
}
if (host.locations) {
//logger.info ('host.locations = ' + JSON.stringify(host.locations, null, 2));
origLocations = [].concat(host.locations);
locationsPromise = internalNginx.renderLocations(host).then((renderedLocations) => {
host.locations = renderedLocations;
});
// Allow someone who is using / custom location path to use it, and skip the default / location
_.map(host.locations, (location) => {
if (location.path === '/') {
host.use_default_location = false;
}
});
} else {
locationsPromise = Promise.resolve();
}
// Set the IPv6 setting for the host
host.ipv6 = internalNginx.ipv6Enabled();
locationsPromise.then(() => {
renderEngine
.parseAndRender(template, host)
.then((config_text) => {
fs.writeFileSync(filename, config_text, {encoding: 'utf8'});
if (debug_mode) {
logger.success('Wrote config:', filename, config_text);
}
// Restore locations array
host.locations = origLocations;
resolve(true);
})
.catch((err) => {
if (debug_mode) {
logger.warn('Could not write ' + filename + ':', err.message);
}
reject(new error.ConfigurationError(err.message));
});
});
});
},
/**
* This generates a temporary nginx config listening on port 80 for the domain names listed
* in the certificate setup. It allows the letsencrypt acme challenge to be requested by letsencrypt
* when requesting a certificate without having a hostname set up already.
*
* @param {Object} certificate
* @returns {Promise}
*/
generateLetsEncryptRequestConfig: (certificate) => {
if (debug_mode) {
logger.info('Generating LetsEncrypt Request Config:', certificate);
}
let renderEngine = new Liquid({
root: __dirname + '/../templates/'
});
return new Promise((resolve, reject) => {
let template = null;
let filename = '/data/nginx/temp/letsencrypt_' + certificate.id + '.conf';
try {
template = fs.readFileSync(__dirname + '/../templates/letsencrypt-request.conf', {encoding: 'utf8'});
} catch (err) {
reject(new error.ConfigurationError(err.message));
return;
}
certificate.ipv6 = internalNginx.ipv6Enabled();
renderEngine
.parseAndRender(template, certificate)
.then((config_text) => {
fs.writeFileSync(filename, config_text, {encoding: 'utf8'});
if (debug_mode) {
logger.success('Wrote config:', filename, config_text);
}
resolve(true);
})
.catch((err) => {
if (debug_mode) {
logger.warn('Could not write ' + filename + ':', err.message);
}
reject(new error.ConfigurationError(err.message));
});
});
},
/**
* This removes the temporary nginx config file generated by `generateLetsEncryptRequestConfig`
*
* @param {Object} certificate
* @param {Boolean} [throw_errors]
* @returns {Promise}
*/
deleteLetsEncryptRequestConfig: (certificate, throw_errors) => {
return new Promise((resolve, reject) => {
try {
let config_file = '/data/nginx/temp/letsencrypt_' + certificate.id + '.conf';
if (debug_mode) {
logger.warn('Deleting nginx config: ' + config_file);
}
fs.unlinkSync(config_file);
} catch (err) {
if (debug_mode) {
logger.warn('Could not delete config:', err.message);
}
if (throw_errors) {
reject(err);
}
}
resolve();
});
},
/**
* @param {String} host_type
* @param {Object} [host]
* @param {Boolean} [throw_errors]
* @returns {Promise}
*/
deleteConfig: (host_type, host, throw_errors) => {
host_type = host_type.replace(new RegExp('-', 'g'), '_');
return new Promise((resolve, reject) => {
try {
let config_file = internalNginx.getConfigName(host_type, typeof host === 'undefined' ? 0 : host.id);
if (debug_mode) {
logger.warn('Deleting nginx config: ' + config_file);
}
fs.unlinkSync(config_file);
} catch (err) {
if (debug_mode) {
logger.warn('Could not delete config:', err.message);
}
if (throw_errors) {
reject(err);
}
}
resolve();
});
},
/**
* @param {String} host_type
* @param {Array} hosts
* @returns {Promise}
*/
bulkGenerateConfigs: (host_type, hosts) => {
let promises = [];
hosts.map(function (host) {
promises.push(internalNginx.generateConfig(host_type, host));
});
return Promise.all(promises);
},
/**
* @param {String} host_type
* @param {Array} hosts
* @param {Boolean} [throw_errors]
* @returns {Promise}
*/
bulkDeleteConfigs: (host_type, hosts, throw_errors) => {
let promises = [];
hosts.map(function (host) {
promises.push(internalNginx.deleteConfig(host_type, host, throw_errors));
});
return Promise.all(promises);
},
/**
* @param {string} config
* @returns {boolean}
*/
advancedConfigHasDefaultLocation: function (config) {
return !!config.match(/^(?:.*;)?\s*?location\s*?\/\s*?{/im);
},
/**
* @returns {boolean}
*/
ipv6Enabled: function () {
if (typeof process.env.DISABLE_IPV6 !== 'undefined') {
const disabled = process.env.DISABLE_IPV6.toLowerCase();
return !(disabled === 'on' || disabled === 'true' || disabled === '1' || disabled === 'yes');
}
return true;
}
};
module.exports = internalNginx;

View File

@ -0,0 +1,466 @@
const _ = require('lodash');
const error = require('../lib/error');
const proxyHostModel = require('../models/proxy_host');
const internalHost = require('./host');
const internalNginx = require('./nginx');
const internalAuditLog = require('./audit-log');
const internalCertificate = require('./certificate');
function omissions () {
return ['is_deleted'];
}
const internalProxyHost = {
/**
* @param {Access} access
* @param {Object} data
* @returns {Promise}
*/
create: (access, data) => {
let create_certificate = data.certificate_id === 'new';
if (create_certificate) {
delete data.certificate_id;
}
return access.can('proxy_hosts:create', data)
.then(() => {
// Get a list of the domain names and check each of them against existing records
let domain_name_check_promises = [];
data.domain_names.map(function (domain_name) {
domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name));
});
return Promise.all(domain_name_check_promises)
.then((check_results) => {
check_results.map(function (result) {
if (result.is_taken) {
throw new error.ValidationError(result.hostname + ' is already in use');
}
});
});
})
.then(() => {
// At this point the domains should have been checked
data.owner_user_id = access.token.getUserId(1);
data = internalHost.cleanSslHstsData(data);
return proxyHostModel
.query()
.omit(omissions())
.insertAndFetch(data);
})
.then((row) => {
if (create_certificate) {
return internalCertificate.createQuickCertificate(access, data)
.then((cert) => {
// update host with cert id
return internalProxyHost.update(access, {
id: row.id,
certificate_id: cert.id
});
})
.then(() => {
return row;
});
} else {
return row;
}
})
.then((row) => {
// re-fetch with cert
return internalProxyHost.get(access, {
id: row.id,
expand: ['certificate', 'owner', 'access_list.[clients,items]']
});
})
.then((row) => {
// Configure nginx
return internalNginx.configure(proxyHostModel, 'proxy_host', row)
.then(() => {
return row;
});
})
.then((row) => {
// Audit log
data.meta = _.assign({}, data.meta || {}, row.meta);
// Add to audit log
return internalAuditLog.add(access, {
action: 'created',
object_type: 'proxy-host',
object_id: row.id,
meta: data
})
.then(() => {
return row;
});
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Number} data.id
* @return {Promise}
*/
update: (access, data) => {
let create_certificate = data.certificate_id === 'new';
if (create_certificate) {
delete data.certificate_id;
}
return access.can('proxy_hosts:update', data.id)
.then((/*access_data*/) => {
// Get a list of the domain names and check each of them against existing records
let domain_name_check_promises = [];
if (typeof data.domain_names !== 'undefined') {
data.domain_names.map(function (domain_name) {
domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, 'proxy', data.id));
});
return Promise.all(domain_name_check_promises)
.then((check_results) => {
check_results.map(function (result) {
if (result.is_taken) {
throw new error.ValidationError(result.hostname + ' is already in use');
}
});
});
}
})
.then(() => {
return internalProxyHost.get(access, {id: data.id});
})
.then((row) => {
if (row.id !== data.id) {
// Sanity check that something crazy hasn't happened
throw new error.InternalValidationError('Proxy Host 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);
data = internalHost.cleanSslHstsData(data, row);
return proxyHostModel
.query()
.where({id: data.id})
.patch(data)
.then((saved_row) => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'updated',
object_type: 'proxy-host',
object_id: row.id,
meta: data
})
.then(() => {
return _.omit(saved_row, omissions());
});
});
})
.then(() => {
return internalProxyHost.get(access, {
id: data.id,
expand: ['owner', 'certificate', 'access_list.[clients,items]']
})
.then((row) => {
if (!row.enabled) {
// No need to add nginx config if host is disabled
return row;
}
// Configure nginx
return internalNginx.configure(proxyHostModel, 'proxy_host', row)
.then((new_meta) => {
row.meta = new_meta;
row = internalHost.cleanRowCertificateMeta(row);
return _.omit(row, omissions());
});
});
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Number} data.id
* @param {Array} [data.expand]
* @param {Array} [data.omit]
* @return {Promise}
*/
get: (access, data) => {
if (typeof data === 'undefined') {
data = {};
}
return access.can('proxy_hosts:get', data.id)
.then((access_data) => {
let query = proxyHostModel
.query()
.where('is_deleted', 0)
.andWhere('id', data.id)
.allowEager('[owner,access_list,access_list.[clients,items],certificate]')
.first();
if (access_data.permission_visibility !== 'all') {
query.andWhere('owner_user_id', access.token.getUserId(1));
}
// Custom omissions
if (typeof data.omit !== 'undefined' && data.omit !== null) {
query.omit(data.omit);
}
if (typeof data.expand !== 'undefined' && data.expand !== null) {
query.eager('[' + data.expand.join(', ') + ']');
}
return query;
})
.then((row) => {
if (row) {
row = internalHost.cleanRowCertificateMeta(row);
return _.omit(row, omissions());
} else {
throw new error.ItemNotFoundError(data.id);
}
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Number} data.id
* @param {String} [data.reason]
* @returns {Promise}
*/
delete: (access, data) => {
return access.can('proxy_hosts:delete', data.id)
.then(() => {
return internalProxyHost.get(access, {id: data.id});
})
.then((row) => {
if (!row) {
throw new error.ItemNotFoundError(data.id);
}
return proxyHostModel
.query()
.where('id', row.id)
.patch({
is_deleted: 1
})
.then(() => {
// Delete Nginx Config
return internalNginx.deleteConfig('proxy_host', row)
.then(() => {
return internalNginx.reload();
});
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'deleted',
object_type: 'proxy-host',
object_id: row.id,
meta: _.omit(row, omissions())
});
});
})
.then(() => {
return true;
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Number} data.id
* @param {String} [data.reason]
* @returns {Promise}
*/
enable: (access, data) => {
return access.can('proxy_hosts:update', data.id)
.then(() => {
return internalProxyHost.get(access, {
id: data.id,
expand: ['certificate', 'owner', 'access_list']
});
})
.then((row) => {
if (!row) {
throw new error.ItemNotFoundError(data.id);
} else if (row.enabled) {
throw new error.ValidationError('Host is already enabled');
}
row.enabled = 1;
return proxyHostModel
.query()
.where('id', row.id)
.patch({
enabled: 1
})
.then(() => {
// Configure nginx
return internalNginx.configure(proxyHostModel, 'proxy_host', row);
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'enabled',
object_type: 'proxy-host',
object_id: row.id,
meta: _.omit(row, omissions())
});
});
})
.then(() => {
return true;
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Number} data.id
* @param {String} [data.reason]
* @returns {Promise}
*/
disable: (access, data) => {
return access.can('proxy_hosts:update', data.id)
.then(() => {
return internalProxyHost.get(access, {id: data.id});
})
.then((row) => {
if (!row) {
throw new error.ItemNotFoundError(data.id);
} else if (!row.enabled) {
throw new error.ValidationError('Host is already disabled');
}
row.enabled = 0;
return proxyHostModel
.query()
.where('id', row.id)
.patch({
enabled: 0
})
.then(() => {
// Delete Nginx Config
return internalNginx.deleteConfig('proxy_host', row)
.then(() => {
return internalNginx.reload();
});
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'disabled',
object_type: 'proxy-host',
object_id: row.id,
meta: _.omit(row, omissions())
});
});
})
.then(() => {
return true;
});
},
/**
* All Hosts
*
* @param {Access} access
* @param {Array} [expand]
* @param {String} [search_query]
* @returns {Promise}
*/
getAll: (access, expand, search_query) => {
return access.can('proxy_hosts:list')
.then((access_data) => {
let query = proxyHostModel
.query()
.where('is_deleted', 0)
.groupBy('id')
.omit(['is_deleted'])
.allowEager('[owner,access_list,certificate]')
.orderBy('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') {
query.where(function () {
this.where('domain_names', 'like', '%' + search_query + '%');
});
}
if (typeof expand !== 'undefined' && expand !== null) {
query.eager('[' + expand.join(', ') + ']');
}
return query;
})
.then((rows) => {
if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) {
return internalHost.cleanAllRowsCertificateMeta(rows);
}
return rows;
});
},
/**
* Report use
*
* @param {Number} user_id
* @param {String} visibility
* @returns {Promise}
*/
getCount: (user_id, visibility) => {
let query = proxyHostModel
.query()
.count('id as count')
.where('is_deleted', 0);
if (visibility !== 'all') {
query.andWhere('owner_user_id', user_id);
}
return query.first()
.then((row) => {
return parseInt(row.count, 10);
});
}
};
module.exports = internalProxyHost;

View File

@ -0,0 +1,461 @@
const _ = require('lodash');
const error = require('../lib/error');
const redirectionHostModel = require('../models/redirection_host');
const internalHost = require('./host');
const internalNginx = require('./nginx');
const internalAuditLog = require('./audit-log');
const internalCertificate = require('./certificate');
function omissions () {
return ['is_deleted'];
}
const internalRedirectionHost = {
/**
* @param {Access} access
* @param {Object} data
* @returns {Promise}
*/
create: (access, data) => {
let create_certificate = data.certificate_id === 'new';
if (create_certificate) {
delete data.certificate_id;
}
return access.can('redirection_hosts:create', data)
.then((/*access_data*/) => {
// Get a list of the domain names and check each of them against existing records
let domain_name_check_promises = [];
data.domain_names.map(function (domain_name) {
domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name));
});
return Promise.all(domain_name_check_promises)
.then((check_results) => {
check_results.map(function (result) {
if (result.is_taken) {
throw new error.ValidationError(result.hostname + ' is already in use');
}
});
});
})
.then(() => {
// At this point the domains should have been checked
data.owner_user_id = access.token.getUserId(1);
data = internalHost.cleanSslHstsData(data);
return redirectionHostModel
.query()
.omit(omissions())
.insertAndFetch(data);
})
.then((row) => {
if (create_certificate) {
return internalCertificate.createQuickCertificate(access, data)
.then((cert) => {
// update host with cert id
return internalRedirectionHost.update(access, {
id: row.id,
certificate_id: cert.id
});
})
.then(() => {
return row;
});
} else {
return row;
}
})
.then((row) => {
// re-fetch with cert
return internalRedirectionHost.get(access, {
id: row.id,
expand: ['certificate', 'owner']
});
})
.then((row) => {
// Configure nginx
return internalNginx.configure(redirectionHostModel, 'redirection_host', row)
.then(() => {
return row;
});
})
.then((row) => {
data.meta = _.assign({}, data.meta || {}, row.meta);
// Add to audit log
return internalAuditLog.add(access, {
action: 'created',
object_type: 'redirection-host',
object_id: row.id,
meta: data
})
.then(() => {
return row;
});
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Number} data.id
* @return {Promise}
*/
update: (access, data) => {
let create_certificate = data.certificate_id === 'new';
if (create_certificate) {
delete data.certificate_id;
}
return access.can('redirection_hosts:update', data.id)
.then((/*access_data*/) => {
// Get a list of the domain names and check each of them against existing records
let domain_name_check_promises = [];
if (typeof data.domain_names !== 'undefined') {
data.domain_names.map(function (domain_name) {
domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, 'redirection', data.id));
});
return Promise.all(domain_name_check_promises)
.then((check_results) => {
check_results.map(function (result) {
if (result.is_taken) {
throw new error.ValidationError(result.hostname + ' is already in use');
}
});
});
}
})
.then(() => {
return internalRedirectionHost.get(access, {id: data.id});
})
.then((row) => {
if (row.id !== data.id) {
// Sanity check that something crazy hasn't happened
throw new error.InternalValidationError('Redirection Host 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);
data = internalHost.cleanSslHstsData(data, row);
return redirectionHostModel
.query()
.where({id: data.id})
.patch(data)
.then((saved_row) => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'updated',
object_type: 'redirection-host',
object_id: row.id,
meta: data
})
.then(() => {
return _.omit(saved_row, omissions());
});
});
})
.then(() => {
return internalRedirectionHost.get(access, {
id: data.id,
expand: ['owner', 'certificate']
})
.then((row) => {
// Configure nginx
return internalNginx.configure(redirectionHostModel, 'redirection_host', row)
.then((new_meta) => {
row.meta = new_meta;
row = internalHost.cleanRowCertificateMeta(row);
return _.omit(row, omissions());
});
});
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Number} data.id
* @param {Array} [data.expand]
* @param {Array} [data.omit]
* @return {Promise}
*/
get: (access, data) => {
if (typeof data === 'undefined') {
data = {};
}
return access.can('redirection_hosts:get', data.id)
.then((access_data) => {
let query = redirectionHostModel
.query()
.where('is_deleted', 0)
.andWhere('id', data.id)
.allowEager('[owner,certificate]')
.first();
if (access_data.permission_visibility !== 'all') {
query.andWhere('owner_user_id', access.token.getUserId(1));
}
// Custom omissions
if (typeof data.omit !== 'undefined' && data.omit !== null) {
query.omit(data.omit);
}
if (typeof data.expand !== 'undefined' && data.expand !== null) {
query.eager('[' + data.expand.join(', ') + ']');
}
return query;
})
.then((row) => {
if (row) {
row = internalHost.cleanRowCertificateMeta(row);
return _.omit(row, omissions());
} else {
throw new error.ItemNotFoundError(data.id);
}
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Number} data.id
* @param {String} [data.reason]
* @returns {Promise}
*/
delete: (access, data) => {
return access.can('redirection_hosts:delete', data.id)
.then(() => {
return internalRedirectionHost.get(access, {id: data.id});
})
.then((row) => {
if (!row) {
throw new error.ItemNotFoundError(data.id);
}
return redirectionHostModel
.query()
.where('id', row.id)
.patch({
is_deleted: 1
})
.then(() => {
// Delete Nginx Config
return internalNginx.deleteConfig('redirection_host', row)
.then(() => {
return internalNginx.reload();
});
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'deleted',
object_type: 'redirection-host',
object_id: row.id,
meta: _.omit(row, omissions())
});
});
})
.then(() => {
return true;
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Number} data.id
* @param {String} [data.reason]
* @returns {Promise}
*/
enable: (access, data) => {
return access.can('redirection_hosts:update', data.id)
.then(() => {
return internalRedirectionHost.get(access, {
id: data.id,
expand: ['certificate', 'owner']
});
})
.then((row) => {
if (!row) {
throw new error.ItemNotFoundError(data.id);
} else if (row.enabled) {
throw new error.ValidationError('Host is already enabled');
}
row.enabled = 1;
return redirectionHostModel
.query()
.where('id', row.id)
.patch({
enabled: 1
})
.then(() => {
// Configure nginx
return internalNginx.configure(redirectionHostModel, 'redirection_host', row);
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'enabled',
object_type: 'redirection-host',
object_id: row.id,
meta: _.omit(row, omissions())
});
});
})
.then(() => {
return true;
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Number} data.id
* @param {String} [data.reason]
* @returns {Promise}
*/
disable: (access, data) => {
return access.can('redirection_hosts:update', data.id)
.then(() => {
return internalRedirectionHost.get(access, {id: data.id});
})
.then((row) => {
if (!row) {
throw new error.ItemNotFoundError(data.id);
} else if (!row.enabled) {
throw new error.ValidationError('Host is already disabled');
}
row.enabled = 0;
return redirectionHostModel
.query()
.where('id', row.id)
.patch({
enabled: 0
})
.then(() => {
// Delete Nginx Config
return internalNginx.deleteConfig('redirection_host', row)
.then(() => {
return internalNginx.reload();
});
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'disabled',
object_type: 'redirection-host',
object_id: row.id,
meta: _.omit(row, omissions())
});
});
})
.then(() => {
return true;
});
},
/**
* All Hosts
*
* @param {Access} access
* @param {Array} [expand]
* @param {String} [search_query]
* @returns {Promise}
*/
getAll: (access, expand, search_query) => {
return access.can('redirection_hosts:list')
.then((access_data) => {
let query = redirectionHostModel
.query()
.where('is_deleted', 0)
.groupBy('id')
.omit(['is_deleted'])
.allowEager('[owner,certificate]')
.orderBy('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') {
query.where(function () {
this.where('domain_names', 'like', '%' + search_query + '%');
});
}
if (typeof expand !== 'undefined' && expand !== null) {
query.eager('[' + expand.join(', ') + ']');
}
return query;
})
.then((rows) => {
if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) {
return internalHost.cleanAllRowsCertificateMeta(rows);
}
return rows;
});
},
/**
* Report use
*
* @param {Number} user_id
* @param {String} visibility
* @returns {Promise}
*/
getCount: (user_id, visibility) => {
let query = redirectionHostModel
.query()
.count('id as count')
.where('is_deleted', 0);
if (visibility !== 'all') {
query.andWhere('owner_user_id', user_id);
}
return query.first()
.then((row) => {
return parseInt(row.count, 10);
});
}
};
module.exports = internalRedirectionHost;

View File

@ -0,0 +1,38 @@
const internalProxyHost = require('./proxy-host');
const internalRedirectionHost = require('./redirection-host');
const internalDeadHost = require('./dead-host');
const internalStream = require('./stream');
const internalReport = {
/**
* @param {Access} access
* @return {Promise}
*/
getHostsReport: (access) => {
return access.can('reports:hosts', 1)
.then((access_data) => {
let user_id = access.token.getUserId(1);
let promises = [
internalProxyHost.getCount(user_id, access_data.visibility),
internalRedirectionHost.getCount(user_id, access_data.visibility),
internalStream.getCount(user_id, access_data.visibility),
internalDeadHost.getCount(user_id, access_data.visibility)
];
return Promise.all(promises);
})
.then((counts) => {
return {
proxy: counts.shift(),
redirection: counts.shift(),
stream: counts.shift(),
dead: counts.shift()
};
});
}
};
module.exports = internalReport;

133
backend/internal/setting.js Normal file
View File

@ -0,0 +1,133 @@
const fs = require('fs');
const error = require('../lib/error');
const settingModel = require('../models/setting');
const internalNginx = require('./nginx');
const internalSetting = {
/**
* @param {Access} access
* @param {Object} data
* @param {String} data.id
* @return {Promise}
*/
update: (access, data) => {
return access.can('settings:update', data.id)
.then((/*access_data*/) => {
return internalSetting.get(access, {id: data.id});
})
.then((row) => {
if (row.id !== data.id) {
// Sanity check that something crazy hasn't happened
throw new error.InternalValidationError('Setting could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
}
return settingModel
.query()
.where({id: data.id})
.patch(data);
})
.then(() => {
return internalSetting.get(access, {
id: data.id
});
})
.then((row) => {
if (row.id === 'default-site') {
// write the html if we need to
if (row.value === 'html') {
fs.writeFileSync('/data/nginx/default_www/index.html', row.meta.html, {encoding: 'utf8'});
}
// Configure nginx
return internalNginx.deleteConfig('default')
.then(() => {
return internalNginx.generateConfig('default', row);
})
.then(() => {
return internalNginx.test();
})
.then(() => {
return internalNginx.reload();
})
.then(() => {
return row;
})
.catch((/*err*/) => {
internalNginx.deleteConfig('default')
.then(() => {
return internalNginx.test();
})
.then(() => {
return internalNginx.reload();
})
.then(() => {
// I'm being slack here I know..
throw new error.ValidationError('Could not reconfigure Nginx. Please check logs.');
});
});
} else {
return row;
}
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {String} data.id
* @return {Promise}
*/
get: (access, data) => {
return access.can('settings:get', data.id)
.then(() => {
return settingModel
.query()
.where('id', data.id)
.first();
})
.then((row) => {
if (row) {
return row;
} else {
throw new error.ItemNotFoundError(data.id);
}
});
},
/**
* This will only count the settings
*
* @param {Access} access
* @returns {*}
*/
getCount: (access) => {
return access.can('settings:list')
.then(() => {
return settingModel
.query()
.count('id as count')
.first();
})
.then((row) => {
return parseInt(row.count, 10);
});
},
/**
* All settings
*
* @param {Access} access
* @returns {Promise}
*/
getAll: (access) => {
return access.can('settings:list')
.then(() => {
return settingModel
.query()
.orderBy('description', 'ASC');
});
}
};
module.exports = internalSetting;

348
backend/internal/stream.js Normal file
View File

@ -0,0 +1,348 @@
const _ = require('lodash');
const error = require('../lib/error');
const streamModel = require('../models/stream');
const internalNginx = require('./nginx');
const internalAuditLog = require('./audit-log');
function omissions () {
return ['is_deleted'];
}
const internalStream = {
/**
* @param {Access} access
* @param {Object} data
* @returns {Promise}
*/
create: (access, data) => {
return access.can('streams:create', data)
.then((/*access_data*/) => {
// TODO: At this point the existing ports should have been checked
data.owner_user_id = access.token.getUserId(1);
if (typeof data.meta === 'undefined') {
data.meta = {};
}
return streamModel
.query()
.omit(omissions())
.insertAndFetch(data);
})
.then((row) => {
// Configure nginx
return internalNginx.configure(streamModel, 'stream', row)
.then(() => {
return internalStream.get(access, {id: row.id, expand: ['owner']});
});
})
.then((row) => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'created',
object_type: 'stream',
object_id: row.id,
meta: data
})
.then(() => {
return row;
});
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Number} data.id
* @return {Promise}
*/
update: (access, data) => {
return access.can('streams:update', data.id)
.then((/*access_data*/) => {
// TODO: at this point the existing streams should have been checked
return internalStream.get(access, {id: data.id});
})
.then((row) => {
if (row.id !== data.id) {
// Sanity check that something crazy hasn't happened
throw new error.InternalValidationError('Stream could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
}
return streamModel
.query()
.omit(omissions())
.patchAndFetchById(row.id, data)
.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, {
action: 'updated',
object_type: 'stream',
object_id: row.id,
meta: data
})
.then(() => {
return _.omit(saved_row, omissions());
});
});
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Number} data.id
* @param {Array} [data.expand]
* @param {Array} [data.omit]
* @return {Promise}
*/
get: (access, data) => {
if (typeof data === 'undefined') {
data = {};
}
return access.can('streams:get', data.id)
.then((access_data) => {
let query = streamModel
.query()
.where('is_deleted', 0)
.andWhere('id', data.id)
.allowEager('[owner]')
.first();
if (access_data.permission_visibility !== 'all') {
query.andWhere('owner_user_id', access.token.getUserId(1));
}
// Custom omissions
if (typeof data.omit !== 'undefined' && data.omit !== null) {
query.omit(data.omit);
}
if (typeof data.expand !== 'undefined' && data.expand !== null) {
query.eager('[' + data.expand.join(', ') + ']');
}
return query;
})
.then((row) => {
if (row) {
return _.omit(row, omissions());
} else {
throw new error.ItemNotFoundError(data.id);
}
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Number} data.id
* @param {String} [data.reason]
* @returns {Promise}
*/
delete: (access, data) => {
return access.can('streams:delete', data.id)
.then(() => {
return internalStream.get(access, {id: data.id});
})
.then((row) => {
if (!row) {
throw new error.ItemNotFoundError(data.id);
}
return streamModel
.query()
.where('id', row.id)
.patch({
is_deleted: 1
})
.then(() => {
// Delete Nginx Config
return internalNginx.deleteConfig('stream', row)
.then(() => {
return internalNginx.reload();
});
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'deleted',
object_type: 'stream',
object_id: row.id,
meta: _.omit(row, omissions())
});
});
})
.then(() => {
return true;
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Number} data.id
* @param {String} [data.reason]
* @returns {Promise}
*/
enable: (access, data) => {
return access.can('streams:update', data.id)
.then(() => {
return internalStream.get(access, {
id: data.id,
expand: ['owner']
});
})
.then((row) => {
if (!row) {
throw new error.ItemNotFoundError(data.id);
} else if (row.enabled) {
throw new error.ValidationError('Host is already enabled');
}
row.enabled = 1;
return streamModel
.query()
.where('id', row.id)
.patch({
enabled: 1
})
.then(() => {
// Configure nginx
return internalNginx.configure(streamModel, 'stream', row);
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'enabled',
object_type: 'stream',
object_id: row.id,
meta: _.omit(row, omissions())
});
});
})
.then(() => {
return true;
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Number} data.id
* @param {String} [data.reason]
* @returns {Promise}
*/
disable: (access, data) => {
return access.can('streams:update', data.id)
.then(() => {
return internalStream.get(access, {id: data.id});
})
.then((row) => {
if (!row) {
throw new error.ItemNotFoundError(data.id);
} else if (!row.enabled) {
throw new error.ValidationError('Host is already disabled');
}
row.enabled = 0;
return streamModel
.query()
.where('id', row.id)
.patch({
enabled: 0
})
.then(() => {
// Delete Nginx Config
return internalNginx.deleteConfig('stream', row)
.then(() => {
return internalNginx.reload();
});
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'disabled',
object_type: 'stream-host',
object_id: row.id,
meta: _.omit(row, omissions())
});
});
})
.then(() => {
return true;
});
},
/**
* All Streams
*
* @param {Access} access
* @param {Array} [expand]
* @param {String} [search_query]
* @returns {Promise}
*/
getAll: (access, expand, search_query) => {
return access.can('streams:list')
.then((access_data) => {
let query = streamModel
.query()
.where('is_deleted', 0)
.groupBy('id')
.omit(['is_deleted'])
.allowEager('[owner]')
.orderBy('incoming_port', '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') {
query.where(function () {
this.where('incoming_port', 'like', '%' + search_query + '%');
});
}
if (typeof expand !== 'undefined' && expand !== null) {
query.eager('[' + expand.join(', ') + ']');
}
return query;
});
},
/**
* Report use
*
* @param {Number} user_id
* @param {String} visibility
* @returns {Promise}
*/
getCount: (user_id, visibility) => {
let query = streamModel
.query()
.count('id as count')
.where('is_deleted', 0);
if (visibility !== 'all') {
query.andWhere('owner_user_id', user_id);
}
return query.first()
.then((row) => {
return parseInt(row.count, 10);
});
}
};
module.exports = internalStream;

162
backend/internal/token.js Normal file
View File

@ -0,0 +1,162 @@
const _ = require('lodash');
const error = require('../lib/error');
const userModel = require('../models/user');
const authModel = require('../models/auth');
const helpers = require('../lib/helpers');
const TokenModel = require('../models/token');
module.exports = {
/**
* @param {Object} data
* @param {String} data.identity
* @param {String} data.secret
* @param {String} [data.scope]
* @param {String} [data.expiry]
* @param {String} [issuer]
* @returns {Promise}
*/
getTokenFromEmail: (data, issuer) => {
let Token = new TokenModel();
data.scope = data.scope || 'user';
data.expiry = data.expiry || '1d';
return userModel
.query()
.where('email', data.identity)
.andWhere('is_deleted', 0)
.andWhere('is_disabled', 0)
.first()
.then((user) => {
if (user) {
// Get auth
return authModel
.query()
.where('user_id', '=', user.id)
.where('type', '=', 'password')
.first()
.then((auth) => {
if (auth) {
return auth.verifyPassword(data.secret)
.then((valid) => {
if (valid) {
if (data.scope !== 'user' && _.indexOf(user.roles, data.scope) === -1) {
// The scope requested doesn't exist as a role against the user,
// you shall not pass.
throw new error.AuthError('Invalid scope: ' + data.scope);
}
// Create a moment of the expiry expression
let expiry = helpers.parseDatePeriod(data.expiry);
if (expiry === null) {
throw new error.AuthError('Invalid expiry time: ' + data.expiry);
}
return Token.create({
iss: issuer || 'api',
attrs: {
id: user.id
},
scope: [data.scope],
expiresIn: data.expiry
})
.then((signed) => {
return {
token: signed.token,
expires: expiry.toISOString()
};
});
} else {
throw new error.AuthError('Invalid password');
}
});
} else {
throw new error.AuthError('No password auth for user');
}
});
} else {
throw new error.AuthError('No relevant user found');
}
});
},
/**
* @param {Access} access
* @param {Object} [data]
* @param {String} [data.expiry]
* @param {String} [data.scope] Only considered if existing token scope is admin
* @returns {Promise}
*/
getFreshToken: (access, data) => {
let Token = new TokenModel();
data = data || {};
data.expiry = data.expiry || '1d';
if (access && access.token.getUserId(0)) {
// Create a moment of the expiry expression
let expiry = helpers.parseDatePeriod(data.expiry);
if (expiry === null) {
throw new error.AuthError('Invalid expiry time: ' + data.expiry);
}
let token_attrs = {
id: access.token.getUserId(0)
};
// Only admins can request otherwise scoped tokens
let scope = access.token.get('scope');
if (data.scope && access.token.hasScope('admin')) {
scope = [data.scope];
if (data.scope === 'job-board' || data.scope === 'worker') {
token_attrs.id = 0;
}
}
return Token.create({
iss: 'api',
scope: scope,
attrs: token_attrs,
expiresIn: data.expiry
})
.then((signed) => {
return {
token: signed.token,
expires: expiry.toISOString()
};
});
} else {
throw new error.AssertionFailedError('Existing token contained invalid user data');
}
},
/**
* @param {Object} user
* @returns {Promise}
*/
getTokenFromUser: (user) => {
const expire = '1d';
const Token = new TokenModel();
const expiry = helpers.parseDatePeriod(expire);
return Token.create({
iss: 'api',
attrs: {
id: user.id
},
scope: ['user'],
expiresIn: expire
})
.then((signed) => {
return {
token: signed.token,
expires: expiry.toISOString(),
user: user
};
});
}
};

518
backend/internal/user.js Normal file
View File

@ -0,0 +1,518 @@
const _ = require('lodash');
const error = require('../lib/error');
const userModel = require('../models/user');
const userPermissionModel = require('../models/user_permission');
const authModel = require('../models/auth');
const gravatar = require('gravatar');
const internalToken = require('./token');
const internalAuditLog = require('./audit-log');
function omissions () {
return ['is_deleted'];
}
const internalUser = {
/**
* @param {Access} access
* @param {Object} data
* @returns {Promise}
*/
create: (access, data) => {
let auth = data.auth || null;
delete data.auth;
data.avatar = data.avatar || '';
data.roles = data.roles || [];
if (typeof data.is_disabled !== 'undefined') {
data.is_disabled = data.is_disabled ? 1 : 0;
}
return access.can('users:create', data)
.then(() => {
data.avatar = gravatar.url(data.email, {default: 'mm'});
return userModel
.query()
.omit(omissions())
.insertAndFetch(data);
})
.then((user) => {
if (auth) {
return authModel
.query()
.insert({
user_id: user.id,
type: auth.type,
secret: auth.secret,
meta: {}
})
.then(() => {
return user;
});
} else {
return user;
}
})
.then((user) => {
// Create permissions row as well
let is_admin = data.roles.indexOf('admin') !== -1;
return userPermissionModel
.query()
.insert({
user_id: user.id,
visibility: is_admin ? 'all' : 'user',
proxy_hosts: 'manage',
redirection_hosts: 'manage',
dead_hosts: 'manage',
streams: 'manage',
access_lists: 'manage',
certificates: 'manage'
})
.then(() => {
return internalUser.get(access, {id: user.id, expand: ['permissions']});
});
})
.then((user) => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'created',
object_type: 'user',
object_id: user.id,
meta: user
})
.then(() => {
return user;
});
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Integer} data.id
* @param {String} [data.email]
* @param {String} [data.name]
* @return {Promise}
*/
update: (access, data) => {
if (typeof data.is_disabled !== 'undefined') {
data.is_disabled = data.is_disabled ? 1 : 0;
}
return access.can('users:update', data.id)
.then(() => {
// Make sure that the user being updated doesn't change their email to another user that is already using it
// 1. get user we want to update
return internalUser.get(access, {id: data.id})
.then((user) => {
// 2. if email is to be changed, find other users with that email
if (typeof data.email !== 'undefined') {
data.email = data.email.toLowerCase().trim();
if (user.email !== data.email) {
return internalUser.isEmailAvailable(data.email, data.id)
.then((available) => {
if (!available) {
throw new error.ValidationError('Email address already in use - ' + data.email);
}
return user;
});
}
}
// No change to email:
return user;
});
})
.then((user) => {
if (user.id !== data.id) {
// Sanity check that something crazy hasn't happened
throw new error.InternalValidationError('User could not be updated, IDs do not match: ' + user.id + ' !== ' + data.id);
}
data.avatar = gravatar.url(data.email || user.email, {default: 'mm'});
return userModel
.query()
.omit(omissions())
.patchAndFetchById(user.id, data)
.then((saved_user) => {
return _.omit(saved_user, omissions());
});
})
.then(() => {
return internalUser.get(access, {id: data.id});
})
.then((user) => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'updated',
object_type: 'user',
object_id: user.id,
meta: data
})
.then(() => {
return user;
});
});
},
/**
* @param {Access} access
* @param {Object} [data]
* @param {Integer} [data.id] Defaults to the token user
* @param {Array} [data.expand]
* @param {Array} [data.omit]
* @return {Promise}
*/
get: (access, data) => {
if (typeof data === 'undefined') {
data = {};
}
if (typeof data.id === 'undefined' || !data.id) {
data.id = access.token.getUserId(0);
}
return access.can('users:get', data.id)
.then(() => {
let query = userModel
.query()
.where('is_deleted', 0)
.andWhere('id', data.id)
.allowEager('[permissions]')
.first();
// Custom omissions
if (typeof data.omit !== 'undefined' && data.omit !== null) {
query.omit(data.omit);
}
if (typeof data.expand !== 'undefined' && data.expand !== null) {
query.eager('[' + data.expand.join(', ') + ']');
}
return query;
})
.then((row) => {
if (row) {
return _.omit(row, omissions());
} else {
throw new error.ItemNotFoundError(data.id);
}
});
},
/**
* Checks if an email address is available, but if a user_id is supplied, it will ignore checking
* against that user.
*
* @param email
* @param user_id
*/
isEmailAvailable: (email, user_id) => {
let query = userModel
.query()
.where('email', '=', email.toLowerCase().trim())
.where('is_deleted', 0)
.first();
if (typeof user_id !== 'undefined') {
query.where('id', '!=', user_id);
}
return query
.then((user) => {
return !user;
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Integer} data.id
* @param {String} [data.reason]
* @returns {Promise}
*/
delete: (access, data) => {
return access.can('users:delete', data.id)
.then(() => {
return internalUser.get(access, {id: data.id});
})
.then((user) => {
if (!user) {
throw new error.ItemNotFoundError(data.id);
}
// Make sure user can't delete themselves
if (user.id === access.token.getUserId(0)) {
throw new error.PermissionError('You cannot delete yourself.');
}
return userModel
.query()
.where('id', user.id)
.patch({
is_deleted: 1
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'deleted',
object_type: 'user',
object_id: user.id,
meta: _.omit(user, omissions())
});
});
})
.then(() => {
return true;
});
},
/**
* This will only count the users
*
* @param {Access} access
* @param {String} [search_query]
* @returns {*}
*/
getCount: (access, search_query) => {
return access.can('users:list')
.then(() => {
let query = userModel
.query()
.count('id as count')
.where('is_deleted', 0)
.first();
// Query is used for searching
if (typeof search_query === 'string') {
query.where(function () {
this.where('user.name', 'like', '%' + search_query + '%')
.orWhere('user.email', 'like', '%' + search_query + '%');
});
}
return query;
})
.then((row) => {
return parseInt(row.count, 10);
});
},
/**
* All users
*
* @param {Access} access
* @param {Array} [expand]
* @param {String} [search_query]
* @returns {Promise}
*/
getAll: (access, expand, search_query) => {
return access.can('users:list')
.then(() => {
let query = userModel
.query()
.where('is_deleted', 0)
.groupBy('id')
.omit(['is_deleted'])
.allowEager('[permissions]')
.orderBy('name', 'ASC');
// Query is used for searching
if (typeof search_query === 'string') {
query.where(function () {
this.where('name', 'like', '%' + search_query + '%')
.orWhere('email', 'like', '%' + search_query + '%');
});
}
if (typeof expand !== 'undefined' && expand !== null) {
query.eager('[' + expand.join(', ') + ']');
}
return query;
});
},
/**
* @param {Access} access
* @param {Integer} [id_requested]
* @returns {[String]}
*/
getUserOmisionsByAccess: (access, id_requested) => {
let response = []; // Admin response
if (!access.token.hasScope('admin') && access.token.getUserId(0) !== id_requested) {
response = ['roles', 'is_deleted']; // Restricted response
}
return response;
},
/**
* @param {Access} access
* @param {Object} data
* @param {Integer} data.id
* @param {String} data.type
* @param {String} data.secret
* @return {Promise}
*/
setPassword: (access, data) => {
return access.can('users:password', data.id)
.then(() => {
return internalUser.get(access, {id: data.id});
})
.then((user) => {
if (user.id !== data.id) {
// Sanity check that something crazy hasn't happened
throw new error.InternalValidationError('User could not be updated, IDs do not match: ' + user.id + ' !== ' + data.id);
}
if (user.id === access.token.getUserId(0)) {
// they're setting their own password. Make sure their current password is correct
if (typeof data.current === 'undefined' || !data.current) {
throw new error.ValidationError('Current password was not supplied');
}
return internalToken.getTokenFromEmail({
identity: user.email,
secret: data.current
})
.then(() => {
return user;
});
}
return user;
})
.then((user) => {
// Get auth, patch if it exists
return authModel
.query()
.where('user_id', user.id)
.andWhere('type', data.type)
.first()
.then((existing_auth) => {
if (existing_auth) {
// patch
return authModel
.query()
.where('user_id', user.id)
.andWhere('type', data.type)
.patch({
type: data.type, // This is required for the model to encrypt on save
secret: data.secret
});
} else {
// insert
return authModel
.query()
.insert({
user_id: user.id,
type: data.type,
secret: data.secret,
meta: {}
});
}
})
.then(() => {
// Add to Audit Log
return internalAuditLog.add(access, {
action: 'updated',
object_type: 'user',
object_id: user.id,
meta: {
name: user.name,
password_changed: true,
auth_type: data.type
}
});
});
})
.then(() => {
return true;
});
},
/**
* @param {Access} access
* @param {Object} data
* @return {Promise}
*/
setPermissions: (access, data) => {
return access.can('users:permissions', data.id)
.then(() => {
return internalUser.get(access, {id: data.id});
})
.then((user) => {
if (user.id !== data.id) {
// Sanity check that something crazy hasn't happened
throw new error.InternalValidationError('User could not be updated, IDs do not match: ' + user.id + ' !== ' + data.id);
}
return user;
})
.then((user) => {
// Get perms row, patch if it exists
return userPermissionModel
.query()
.where('user_id', user.id)
.first()
.then((existing_auth) => {
if (existing_auth) {
// patch
return userPermissionModel
.query()
.where('user_id', user.id)
.patchAndFetchById(existing_auth.id, _.assign({user_id: user.id}, data));
} else {
// insert
return userPermissionModel
.query()
.insertAndFetch(_.assign({user_id: user.id}, data));
}
})
.then((permissions) => {
// Add to Audit Log
return internalAuditLog.add(access, {
action: 'updated',
object_type: 'user',
object_id: user.id,
meta: {
name: user.name,
permissions: permissions
}
});
});
})
.then(() => {
return true;
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Integer} data.id
*/
loginAs: (access, data) => {
return access.can('users:loginas', data.id)
.then(() => {
return internalUser.get(access, data);
})
.then((user) => {
return internalToken.getTokenFromUser(user);
});
}
};
module.exports = internalUser;

19
backend/knexfile.js Normal file
View File

@ -0,0 +1,19 @@
module.exports = {
development: {
client: 'mysql',
migrations: {
tableName: 'migrations',
stub: 'lib/migrate_template.js',
directory: 'migrations'
}
},
production: {
client: 'mysql',
migrations: {
tableName: 'migrations',
stub: 'lib/migrate_template.js',
directory: 'migrations'
}
}
};

314
backend/lib/access.js Normal file
View File

@ -0,0 +1,314 @@
/**
* Some Notes: This is a friggin complicated piece of code.
*
* "scope" in this file means "where did this token come from and what is using it", so 99% of the time
* the "scope" is going to be "user" because it would be a user token. This is not to be confused with
* the "role" which could be "user" or "admin". The scope in fact, could be "worker" or anything else.
*
*
*/
const _ = require('lodash');
const logger = require('../logger').access;
const validator = require('ajv');
const error = require('./error');
const userModel = require('../models/user');
const proxyHostModel = require('../models/proxy_host');
const TokenModel = require('../models/token');
const roleSchema = require('./access/roles.json');
const permsSchema = require('./access/permissions.json');
module.exports = function (token_string) {
let Token = new TokenModel();
let token_data = null;
let initialised = false;
let object_cache = {};
let allow_internal_access = false;
let user_roles = [];
let permissions = {};
/**
* Loads the Token object from the token string
*
* @returns {Promise}
*/
this.init = () => {
return new Promise((resolve, reject) => {
if (initialised) {
resolve();
} else if (!token_string) {
reject(new error.PermissionError('Permission Denied'));
} else {
resolve(Token.load(token_string)
.then((data) => {
token_data = data;
// At this point we need to load the user from the DB and make sure they:
// - exist (and not soft deleted)
// - still have the appropriate scopes for this token
// This is only required when the User ID is supplied or if the token scope has `user`
if (token_data.attrs.id || (typeof token_data.scope !== 'undefined' && _.indexOf(token_data.scope, 'user') !== -1)) {
// Has token user id or token user scope
return userModel
.query()
.where('id', token_data.attrs.id)
.andWhere('is_deleted', 0)
.andWhere('is_disabled', 0)
.allowEager('[permissions]')
.eager('[permissions]')
.first()
.then((user) => {
if (user) {
// make sure user has all scopes of the token
// The `user` role is not added against the user row, so we have to just add it here to get past this check.
user.roles.push('user');
let is_ok = true;
_.forEach(token_data.scope, (scope_item) => {
if (_.indexOf(user.roles, scope_item) === -1) {
is_ok = false;
}
});
if (!is_ok) {
throw new error.AuthError('Invalid token scope for User');
} else {
initialised = true;
user_roles = user.roles;
permissions = user.permissions;
}
} else {
throw new error.AuthError('User cannot be loaded for Token');
}
});
} else {
initialised = true;
}
}));
}
});
};
/**
* Fetches the object ids from the database, only once per object type, for this token.
* This only applies to USER token scopes, as all other tokens are not really bound
* by object scopes
*
* @param {String} object_type
* @returns {Promise}
*/
this.loadObjects = (object_type) => {
return new Promise((resolve, reject) => {
if (Token.hasScope('user')) {
if (typeof token_data.attrs.id === 'undefined' || !token_data.attrs.id) {
reject(new error.AuthError('User Token supplied without a User ID'));
} else {
let token_user_id = token_data.attrs.id ? token_data.attrs.id : 0;
let query;
if (typeof object_cache[object_type] === 'undefined') {
switch (object_type) {
// USERS - should only return yourself
case 'users':
resolve(token_user_id ? [token_user_id] : []);
break;
// Proxy Hosts
case 'proxy_hosts':
query = proxyHostModel
.query()
.select('id')
.andWhere('is_deleted', 0);
if (permissions.visibility === 'user') {
query.andWhere('owner_user_id', token_user_id);
}
resolve(query
.then((rows) => {
let result = [];
_.forEach(rows, (rule_row) => {
result.push(rule_row.id);
});
// enum should not have less than 1 item
if (!result.length) {
result.push(0);
}
return result;
})
);
break;
// DEFAULT: null
default:
resolve(null);
break;
}
} else {
resolve(object_cache[object_type]);
}
}
} else {
resolve(null);
}
})
.then((objects) => {
object_cache[object_type] = objects;
return objects;
});
};
/**
* Creates a schema object on the fly with the IDs and other values required to be checked against the permissionSchema
*
* @param {String} permission_label
* @returns {Object}
*/
this.getObjectSchema = (permission_label) => {
let base_object_type = permission_label.split(':').shift();
let schema = {
$id: 'objects',
$schema: 'http://json-schema.org/draft-07/schema#',
description: 'Actor Properties',
type: 'object',
additionalProperties: false,
properties: {
user_id: {
anyOf: [
{
type: 'number',
enum: [Token.get('attrs').id]
}
]
},
scope: {
type: 'string',
pattern: '^' + Token.get('scope') + '$'
}
}
};
return this.loadObjects(base_object_type)
.then((object_result) => {
if (typeof object_result === 'object' && object_result !== null) {
schema.properties[base_object_type] = {
type: 'number',
enum: object_result,
minimum: 1
};
} else {
schema.properties[base_object_type] = {
type: 'number',
minimum: 1
};
}
return schema;
});
};
return {
token: Token,
/**
*
* @param {Boolean} [allow_internal]
* @returns {Promise}
*/
load: (allow_internal) => {
return new Promise(function (resolve/*, reject*/) {
if (token_string) {
resolve(Token.load(token_string));
} else {
allow_internal_access = allow_internal;
resolve(allow_internal_access || null);
}
});
},
reloadObjects: this.loadObjects,
/**
*
* @param {String} permission
* @param {*} [data]
* @returns {Promise}
*/
can: (permission, data) => {
if (allow_internal_access === true) {
return Promise.resolve(true);
//return true;
} else {
return this.init()
.then(() => {
// Initialised, token decoded ok
return this.getObjectSchema(permission)
.then((objectSchema) => {
let data_schema = {
[permission]: {
data: data,
scope: Token.get('scope'),
roles: user_roles,
permission_visibility: permissions.visibility,
permission_proxy_hosts: permissions.proxy_hosts,
permission_redirection_hosts: permissions.redirection_hosts,
permission_dead_hosts: permissions.dead_hosts,
permission_streams: permissions.streams,
permission_access_lists: permissions.access_lists,
permission_certificates: permissions.certificates
}
};
let permissionSchema = {
$schema: 'http://json-schema.org/draft-07/schema#',
$async: true,
$id: 'permissions',
additionalProperties: false,
properties: {}
};
permissionSchema.properties[permission] = require('./access/' + permission.replace(/:/gim, '-') + '.json');
// logger.info('objectSchema', JSON.stringify(objectSchema, null, 2));
// logger.info('permissionSchema', JSON.stringify(permissionSchema, null, 2));
// logger.info('data_schema', JSON.stringify(data_schema, null, 2));
let ajv = validator({
verbose: true,
allErrors: true,
format: 'full',
missingRefs: 'fail',
breakOnError: true,
coerceTypes: true,
schemas: [
roleSchema,
permsSchema,
objectSchema,
permissionSchema
]
});
return ajv.validate('permissions', data_schema)
.then(() => {
return data_schema[permission];
});
});
})
.catch((err) => {
err.permission = permission;
err.permission_data = data;
logger.error(permission, data, err.message);
throw new error.PermissionError('Permission Denied', err);
});
}
}
};
};

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["permission_access_lists", "roles"],
"properties": {
"permission_access_lists": {
"$ref": "perms#/definitions/manage"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["user"]
}
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["permission_access_lists", "roles"],
"properties": {
"permission_access_lists": {
"$ref": "perms#/definitions/manage"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["user"]
}
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["permission_access_lists", "roles"],
"properties": {
"permission_access_lists": {
"$ref": "perms#/definitions/view"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["user"]
}
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["permission_access_lists", "roles"],
"properties": {
"permission_access_lists": {
"$ref": "perms#/definitions/view"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["user"]
}
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["permission_access_lists", "roles"],
"properties": {
"permission_access_lists": {
"$ref": "perms#/definitions/manage"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["user"]
}
}
}
}
]
}

View File

@ -0,0 +1,7 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["permission_certificates", "roles"],
"properties": {
"permission_certificates": {
"$ref": "perms#/definitions/manage"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["user"]
}
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["permission_certificates", "roles"],
"properties": {
"permission_certificates": {
"$ref": "perms#/definitions/manage"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["user"]
}
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["permission_certificates", "roles"],
"properties": {
"permission_certificates": {
"$ref": "perms#/definitions/view"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["user"]
}
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["permission_certificates", "roles"],
"properties": {
"permission_certificates": {
"$ref": "perms#/definitions/view"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["user"]
}
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["permission_certificates", "roles"],
"properties": {
"permission_certificates": {
"$ref": "perms#/definitions/manage"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["user"]
}
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["permission_dead_hosts", "roles"],
"properties": {
"permission_dead_hosts": {
"$ref": "perms#/definitions/manage"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["user"]
}
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["permission_dead_hosts", "roles"],
"properties": {
"permission_dead_hosts": {
"$ref": "perms#/definitions/manage"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["user"]
}
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["permission_dead_hosts", "roles"],
"properties": {
"permission_dead_hosts": {
"$ref": "perms#/definitions/view"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["user"]
}
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["permission_dead_hosts", "roles"],
"properties": {
"permission_dead_hosts": {
"$ref": "perms#/definitions/view"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["user"]
}
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["permission_dead_hosts", "roles"],
"properties": {
"permission_dead_hosts": {
"$ref": "perms#/definitions/manage"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["user"]
}
}
}
}
]
}

View File

@ -0,0 +1,14 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "perms",
"definitions": {
"view": {
"type": "string",
"pattern": "^(view|manage)$"
},
"manage": {
"type": "string",
"pattern": "^(manage)$"
}
}
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["permission_proxy_hosts", "roles"],
"properties": {
"permission_proxy_hosts": {
"$ref": "perms#/definitions/manage"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["user"]
}
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["permission_proxy_hosts", "roles"],
"properties": {
"permission_proxy_hosts": {
"$ref": "perms#/definitions/manage"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["user"]
}
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["permission_proxy_hosts", "roles"],
"properties": {
"permission_proxy_hosts": {
"$ref": "perms#/definitions/view"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["user"]
}
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["permission_proxy_hosts", "roles"],
"properties": {
"permission_proxy_hosts": {
"$ref": "perms#/definitions/view"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["user"]
}
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["permission_proxy_hosts", "roles"],
"properties": {
"permission_proxy_hosts": {
"$ref": "perms#/definitions/manage"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["user"]
}
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["permission_redirection_hosts", "roles"],
"properties": {
"permission_redirection_hosts": {
"$ref": "perms#/definitions/manage"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["user"]
}
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["permission_redirection_hosts", "roles"],
"properties": {
"permission_redirection_hosts": {
"$ref": "perms#/definitions/manage"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["user"]
}
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["permission_redirection_hosts", "roles"],
"properties": {
"permission_redirection_hosts": {
"$ref": "perms#/definitions/view"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["user"]
}
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["permission_redirection_hosts", "roles"],
"properties": {
"permission_redirection_hosts": {
"$ref": "perms#/definitions/view"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["user"]
}
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["permission_redirection_hosts", "roles"],
"properties": {
"permission_redirection_hosts": {
"$ref": "perms#/definitions/manage"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["user"]
}
}
}
}
]
}

View File

@ -0,0 +1,7 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/user"
}
]
}

View File

@ -0,0 +1,39 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "roles",
"definitions": {
"admin": {
"type": "object",
"required": ["scope", "roles"],
"properties": {
"scope": {
"type": "array",
"contains": {
"type": "string",
"pattern": "^user$"
}
},
"roles": {
"type": "array",
"contains": {
"type": "string",
"pattern": "^admin$"
}
}
}
},
"user": {
"type": "object",
"required": ["scope"],
"properties": {
"scope": {
"type": "array",
"contains": {
"type": "string",
"pattern": "^user$"
}
}
}
}
}
}

View File

@ -0,0 +1,7 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
}
]
}

View File

@ -0,0 +1,7 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
}
]
}

View File

@ -0,0 +1,7 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["permission_streams", "roles"],
"properties": {
"permission_streams": {
"$ref": "perms#/definitions/manage"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["user"]
}
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["permission_streams", "roles"],
"properties": {
"permission_streams": {
"$ref": "perms#/definitions/manage"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["user"]
}
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["permission_streams", "roles"],
"properties": {
"permission_streams": {
"$ref": "perms#/definitions/view"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["user"]
}
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["permission_streams", "roles"],
"properties": {
"permission_streams": {
"$ref": "perms#/definitions/view"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["user"]
}
}
}
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["permission_streams", "roles"],
"properties": {
"permission_streams": {
"$ref": "perms#/definitions/manage"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": ["user"]
}
}
}
}
]
}

View File

@ -0,0 +1,7 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
}
]
}

View File

@ -0,0 +1,7 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["data", "scope"],
"properties": {
"data": {
"$ref": "objects#/properties/users"
},
"scope": {
"type": "array",
"contains": {
"type": "string",
"pattern": "^user$"
}
}
}
}
]
}

View File

@ -0,0 +1,7 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
}
]
}

View File

@ -0,0 +1,7 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["data", "scope"],
"properties": {
"data": {
"$ref": "objects#/properties/users"
},
"scope": {
"type": "array",
"contains": {
"type": "string",
"pattern": "^user$"
}
}
}
}
]
}

View File

@ -0,0 +1,7 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
}
]
}

View File

@ -0,0 +1,23 @@
{
"anyOf": [
{
"$ref": "roles#/definitions/admin"
},
{
"type": "object",
"required": ["data", "scope"],
"properties": {
"data": {
"$ref": "objects#/properties/users"
},
"scope": {
"type": "array",
"contains": {
"type": "string",
"pattern": "^user$"
}
}
}
}
]
}

90
backend/lib/error.js Normal file
View File

@ -0,0 +1,90 @@
const _ = require('lodash');
const util = require('util');
module.exports = {
PermissionError: function (message, previous) {
Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name;
this.previous = previous;
this.message = 'Permission Denied';
this.public = true;
this.status = 403;
},
ItemNotFoundError: function (id, previous) {
Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name;
this.previous = previous;
this.message = 'Item Not Found - ' + id;
this.public = true;
this.status = 404;
},
AuthError: function (message, previous) {
Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name;
this.previous = previous;
this.message = message;
this.public = true;
this.status = 401;
},
InternalError: function (message, previous) {
Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name;
this.previous = previous;
this.message = message;
this.status = 500;
this.public = false;
},
InternalValidationError: function (message, previous) {
Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name;
this.previous = previous;
this.message = message;
this.status = 400;
this.public = false;
},
ConfigurationError: function (message, previous) {
Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name;
this.previous = previous;
this.message = message;
this.status = 400;
this.public = true;
},
CacheError: function (message, previous) {
Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name;
this.message = message;
this.previous = previous;
this.status = 500;
this.public = false;
},
ValidationError: function (message, previous) {
Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name;
this.previous = previous;
this.message = message;
this.public = true;
this.status = 400;
},
AssertionFailedError: function (message, previous) {
Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name;
this.previous = previous;
this.message = message;
this.public = false;
this.status = 400;
}
};
_.forEach(module.exports, function (error) {
util.inherits(error, Error);
});

View File

@ -0,0 +1,40 @@
const validator = require('../validator');
module.exports = function (req, res, next) {
if (req.headers.origin) {
const originSchema = {
oneOf: [
{
type: 'string',
pattern: '^[a-z\\-]+:\\/\\/(?:[\\w\\-\\.]+(:[0-9]+)?/?)?$'
},
{
type: 'string',
pattern: '^[a-z\\-]+:\\/\\/(?:\\[([a-z0-9]{0,4}\\:?)+\\])?/?(:[0-9]+)?$'
}
]
};
// very relaxed validation....
validator(originSchema, req.headers.origin)
.then(function () {
res.set({
'Access-Control-Allow-Origin': req.headers.origin,
'Access-Control-Allow-Credentials': true,
'Access-Control-Allow-Methods': 'OPTIONS, GET, POST',
'Access-Control-Allow-Headers': 'Content-Type, Cache-Control, Pragma, Expires, Authorization, X-Dataset-Total, X-Dataset-Offset, X-Dataset-Limit',
'Access-Control-Max-Age': 5 * 60,
'Access-Control-Expose-Headers': 'X-Dataset-Total, X-Dataset-Offset, X-Dataset-Limit'
});
next();
})
.catch(next);
} else {
// No origin
next();
}
};

View File

@ -0,0 +1,15 @@
const Access = require('../access');
module.exports = () => {
return function (req, res, next) {
res.locals.access = null;
let access = new Access(res.locals.token || null);
access.load()
.then(() => {
res.locals.access = access;
next();
})
.catch(next);
};
};

View File

@ -0,0 +1,13 @@
module.exports = function () {
return function (req, res, next) {
if (req.headers.authorization) {
let parts = req.headers.authorization.split(' ');
if (parts && parts[0] === 'Bearer' && parts[1]) {
res.locals.token = parts[1];
}
}
next();
};
};

View File

@ -0,0 +1,55 @@
let _ = require('lodash');
module.exports = function (default_sort, default_offset, default_limit, max_limit) {
/**
* This will setup the req query params with filtered data and defaults
*
* sort will be an array of fields and their direction
* offset will be an int, defaulting to zero if no other default supplied
* limit will be an int, defaulting to 50 if no other default supplied, and limited to the max if that was supplied
*
*/
return function (req, res, next) {
req.query.offset = typeof req.query.limit === 'undefined' ? default_offset || 0 : parseInt(req.query.offset, 10);
req.query.limit = typeof req.query.limit === 'undefined' ? default_limit || 50 : parseInt(req.query.limit, 10);
if (max_limit && req.query.limit > max_limit) {
req.query.limit = max_limit;
}
// Sorting
let sort = typeof req.query.sort === 'undefined' ? default_sort : req.query.sort;
let myRegexp = /.*\.(asc|desc)$/ig;
let sort_array = [];
sort = sort.split(',');
_.map(sort, function (val) {
let matches = myRegexp.exec(val);
if (matches !== null) {
let dir = matches[1];
sort_array.push({
field: val.substr(0, val.length - (dir.length + 1)),
dir: dir.toLowerCase()
});
} else {
sort_array.push({
field: val,
dir: 'asc'
});
}
});
// Sort will now be in this format:
// [
// { field: 'field1', dir: 'asc' },
// { field: 'field2', dir: 'desc' }
// ]
req.query.sort = sort_array;
next();
};
};

View File

@ -0,0 +1,9 @@
module.exports = (req, res, next) => {
if (req.params.user_id === 'me' && res.locals.access) {
req.params.user_id = res.locals.access.token.get('attrs').id;
} else {
req.params.user_id = parseInt(req.params.user_id, 10);
}
next();
};

32
backend/lib/helpers.js Normal file
View File

@ -0,0 +1,32 @@
const moment = require('moment');
module.exports = {
/**
* Takes an expression such as 30d and returns a moment object of that date in future
*
* Key Shorthand
* ==================
* years y
* quarters Q
* months M
* weeks w
* days d
* hours h
* minutes m
* seconds s
* milliseconds ms
*
* @param {String} expression
* @returns {Object}
*/
parseDatePeriod: function (expression) {
let matches = expression.match(/^([0-9]+)(y|Q|M|w|d|h|m|s|ms)$/m);
if (matches) {
return moment().add(matches[1], matches[2]);
}
return null;
}
};

View File

@ -0,0 +1,55 @@
const migrate_name = 'identifier_for_migrate';
const logger = require('../logger').migrate;
/**
* Migrate
*
* @see http://knexjs.org/#Schema
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.up = function (knex, Promise) {
logger.info('[' + migrate_name + '] Migrating Up...');
// Create Table example:
/*return knex.schema.createTable('notification', (table) => {
table.increments().primary();
table.string('name').notNull();
table.string('type').notNull();
table.integer('created_on').notNull();
table.integer('modified_on').notNull();
})
.then(function () {
logger.info('[' + migrate_name + '] Notification Table created');
});*/
logger.info('[' + migrate_name + '] Migrating Up Complete');
return Promise.resolve(true);
};
/**
* Undo Migrate
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.down = function (knex, Promise) {
logger.info('[' + migrate_name + '] Migrating Down...');
// Drop table example:
/*return knex.schema.dropTable('notification')
.then(() => {
logger.info('[' + migrate_name + '] Notification Table dropped');
});*/
logger.info('[' + migrate_name + '] Migrating Down Complete');
return Promise.resolve(true);
};

20
backend/lib/utils.js Normal file
View File

@ -0,0 +1,20 @@
const exec = require('child_process').exec;
module.exports = {
/**
* @param {String} cmd
* @returns {Promise}
*/
exec: function (cmd) {
return new Promise((resolve, reject) => {
exec(cmd, function (err, stdout, /*stderr*/) {
if (err && typeof err === 'object') {
reject(err);
} else {
resolve(stdout.trim());
}
});
});
}
};

View File

@ -0,0 +1,45 @@
const error = require('../error');
const path = require('path');
const parser = require('json-schema-ref-parser');
const ajv = require('ajv')({
verbose: true,
validateSchema: true,
allErrors: false,
format: 'full',
coerceTypes: true
});
/**
* @param {Object} schema
* @param {Object} payload
* @returns {Promise}
*/
function apiValidator (schema, payload/*, description*/) {
return new Promise(function Promise_apiValidator (resolve, reject) {
if (typeof payload === 'undefined') {
reject(new error.ValidationError('Payload is undefined'));
}
let validate = ajv.compile(schema);
let valid = validate(payload);
if (valid && !validate.errors) {
resolve(payload);
} else {
let message = ajv.errorsText(validate.errors);
let err = new error.ValidationError(message);
err.debug = [validate.errors, payload];
reject(err);
}
});
}
apiValidator.loadSchemas = parser
.dereference(path.resolve('schema/index.json'))
.then((schema) => {
ajv.addSchema(schema);
return schema;
});
module.exports = apiValidator;

View File

@ -0,0 +1,49 @@
const _ = require('lodash');
const error = require('../error');
const definitions = require('../../schema/definitions.json');
RegExp.prototype.toJSON = RegExp.prototype.toString;
const ajv = require('ajv')({
verbose: true, //process.env.NODE_ENV === 'development',
allErrors: true,
format: 'full', // strict regexes for format checks
coerceTypes: true,
schemas: [
definitions
]
});
/**
*
* @param {Object} schema
* @param {Object} payload
* @returns {Promise}
*/
function validator (schema, payload) {
return new Promise(function (resolve, reject) {
if (!payload) {
reject(new error.InternalValidationError('Payload is falsy'));
} else {
try {
let validate = ajv.compile(schema);
let valid = validate(payload);
if (valid && !validate.errors) {
resolve(_.cloneDeep(payload));
} else {
let message = ajv.errorsText(validate.errors);
reject(new error.InternalValidationError(message));
}
} catch (err) {
reject(err);
}
}
});
}
module.exports = validator;

13
backend/logger.js Normal file
View File

@ -0,0 +1,13 @@
const {Signale} = require('signale');
module.exports = {
global: new Signale({scope: 'Global '}),
migrate: new Signale({scope: 'Migrate '}),
express: new Signale({scope: 'Express '}),
access: new Signale({scope: 'Access '}),
nginx: new Signale({scope: 'Nginx '}),
ssl: new Signale({scope: 'SSL '}),
import: new Signale({scope: 'Importer '}),
setup: new Signale({scope: 'Setup '}),
ip_ranges: new Signale({scope: 'IP Ranges'})
};

15
backend/migrate.js Normal file
View File

@ -0,0 +1,15 @@
const db = require('./db');
const logger = require('./logger').migrate;
module.exports = {
latest: function () {
return db.migrate.currentVersion()
.then((version) => {
logger.info('Current database version:', version);
return db.migrate.latest({
tableName: 'migrations',
directory: 'migrations'
});
});
}
};

View File

@ -0,0 +1,205 @@
const migrate_name = 'initial-schema';
const logger = require('../logger').migrate;
/**
* Migrate
*
* @see http://knexjs.org/#Schema
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.up = function (knex/*, Promise*/) {
logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema.createTable('auth', (table) => {
table.increments().primary();
table.dateTime('created_on').notNull();
table.dateTime('modified_on').notNull();
table.integer('user_id').notNull().unsigned();
table.string('type', 30).notNull();
table.string('secret').notNull();
table.json('meta').notNull();
table.integer('is_deleted').notNull().unsigned().defaultTo(0);
})
.then(() => {
logger.info('[' + migrate_name + '] auth Table created');
return knex.schema.createTable('user', (table) => {
table.increments().primary();
table.dateTime('created_on').notNull();
table.dateTime('modified_on').notNull();
table.integer('is_deleted').notNull().unsigned().defaultTo(0);
table.integer('is_disabled').notNull().unsigned().defaultTo(0);
table.string('email').notNull();
table.string('name').notNull();
table.string('nickname').notNull();
table.string('avatar').notNull();
table.json('roles').notNull();
});
})
.then(() => {
logger.info('[' + migrate_name + '] user Table created');
return knex.schema.createTable('user_permission', (table) => {
table.increments().primary();
table.dateTime('created_on').notNull();
table.dateTime('modified_on').notNull();
table.integer('user_id').notNull().unsigned();
table.string('visibility').notNull();
table.string('proxy_hosts').notNull();
table.string('redirection_hosts').notNull();
table.string('dead_hosts').notNull();
table.string('streams').notNull();
table.string('access_lists').notNull();
table.string('certificates').notNull();
table.unique('user_id');
});
})
.then(() => {
logger.info('[' + migrate_name + '] user_permission Table created');
return knex.schema.createTable('proxy_host', (table) => {
table.increments().primary();
table.dateTime('created_on').notNull();
table.dateTime('modified_on').notNull();
table.integer('owner_user_id').notNull().unsigned();
table.integer('is_deleted').notNull().unsigned().defaultTo(0);
table.json('domain_names').notNull();
table.string('forward_ip').notNull();
table.integer('forward_port').notNull().unsigned();
table.integer('access_list_id').notNull().unsigned().defaultTo(0);
table.integer('certificate_id').notNull().unsigned().defaultTo(0);
table.integer('ssl_forced').notNull().unsigned().defaultTo(0);
table.integer('caching_enabled').notNull().unsigned().defaultTo(0);
table.integer('block_exploits').notNull().unsigned().defaultTo(0);
table.text('advanced_config').notNull().defaultTo('');
table.json('meta').notNull();
});
})
.then(() => {
logger.info('[' + migrate_name + '] proxy_host Table created');
return knex.schema.createTable('redirection_host', (table) => {
table.increments().primary();
table.dateTime('created_on').notNull();
table.dateTime('modified_on').notNull();
table.integer('owner_user_id').notNull().unsigned();
table.integer('is_deleted').notNull().unsigned().defaultTo(0);
table.json('domain_names').notNull();
table.string('forward_domain_name').notNull();
table.integer('preserve_path').notNull().unsigned().defaultTo(0);
table.integer('certificate_id').notNull().unsigned().defaultTo(0);
table.integer('ssl_forced').notNull().unsigned().defaultTo(0);
table.integer('block_exploits').notNull().unsigned().defaultTo(0);
table.text('advanced_config').notNull().defaultTo('');
table.json('meta').notNull();
});
})
.then(() => {
logger.info('[' + migrate_name + '] redirection_host Table created');
return knex.schema.createTable('dead_host', (table) => {
table.increments().primary();
table.dateTime('created_on').notNull();
table.dateTime('modified_on').notNull();
table.integer('owner_user_id').notNull().unsigned();
table.integer('is_deleted').notNull().unsigned().defaultTo(0);
table.json('domain_names').notNull();
table.integer('certificate_id').notNull().unsigned().defaultTo(0);
table.integer('ssl_forced').notNull().unsigned().defaultTo(0);
table.text('advanced_config').notNull().defaultTo('');
table.json('meta').notNull();
});
})
.then(() => {
logger.info('[' + migrate_name + '] dead_host Table created');
return knex.schema.createTable('stream', (table) => {
table.increments().primary();
table.dateTime('created_on').notNull();
table.dateTime('modified_on').notNull();
table.integer('owner_user_id').notNull().unsigned();
table.integer('is_deleted').notNull().unsigned().defaultTo(0);
table.integer('incoming_port').notNull().unsigned();
table.string('forward_ip').notNull();
table.integer('forwarding_port').notNull().unsigned();
table.integer('tcp_forwarding').notNull().unsigned().defaultTo(0);
table.integer('udp_forwarding').notNull().unsigned().defaultTo(0);
table.json('meta').notNull();
});
})
.then(() => {
logger.info('[' + migrate_name + '] stream Table created');
return knex.schema.createTable('access_list', (table) => {
table.increments().primary();
table.dateTime('created_on').notNull();
table.dateTime('modified_on').notNull();
table.integer('owner_user_id').notNull().unsigned();
table.integer('is_deleted').notNull().unsigned().defaultTo(0);
table.string('name').notNull();
table.json('meta').notNull();
});
})
.then(() => {
logger.info('[' + migrate_name + '] access_list Table created');
return knex.schema.createTable('certificate', (table) => {
table.increments().primary();
table.dateTime('created_on').notNull();
table.dateTime('modified_on').notNull();
table.integer('owner_user_id').notNull().unsigned();
table.integer('is_deleted').notNull().unsigned().defaultTo(0);
table.string('provider').notNull();
table.string('nice_name').notNull().defaultTo('');
table.json('domain_names').notNull();
table.dateTime('expires_on').notNull();
table.json('meta').notNull();
});
})
.then(() => {
logger.info('[' + migrate_name + '] certificate Table created');
return knex.schema.createTable('access_list_auth', (table) => {
table.increments().primary();
table.dateTime('created_on').notNull();
table.dateTime('modified_on').notNull();
table.integer('access_list_id').notNull().unsigned();
table.string('username').notNull();
table.string('password').notNull();
table.json('meta').notNull();
});
})
.then(() => {
logger.info('[' + migrate_name + '] access_list_auth Table created');
return knex.schema.createTable('audit_log', (table) => {
table.increments().primary();
table.dateTime('created_on').notNull();
table.dateTime('modified_on').notNull();
table.integer('user_id').notNull().unsigned();
table.string('object_type').notNull().defaultTo('');
table.integer('object_id').notNull().unsigned().defaultTo(0);
table.string('action').notNull();
table.json('meta').notNull();
});
})
.then(() => {
logger.info('[' + migrate_name + '] audit_log Table created');
});
};
/**
* Undo Migrate
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.down = function (knex, Promise) {
logger.warn('[' + migrate_name + '] You can\'t migrate down the initial data.');
return Promise.resolve(true);
};

View File

@ -0,0 +1,35 @@
const migrate_name = 'websockets';
const logger = require('../logger').migrate;
/**
* Migrate
*
* @see http://knexjs.org/#Schema
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.up = function (knex/*, Promise*/) {
logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema.table('proxy_host', function (proxy_host) {
proxy_host.integer('allow_websocket_upgrade').notNull().unsigned().defaultTo(0);
})
.then(() => {
logger.info('[' + migrate_name + '] proxy_host Table altered');
});
};
/**
* Undo Migrate
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.down = function (knex, Promise) {
logger.warn('[' + migrate_name + '] You can\'t migrate down this one.');
return Promise.resolve(true);
};

View File

@ -0,0 +1,34 @@
const migrate_name = 'forward_host';
const logger = require('../logger').migrate;
/**
* Migrate
*
* @see http://knexjs.org/#Schema
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.up = function (knex/*, Promise*/) {
logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema.table('proxy_host', function (proxy_host) {
proxy_host.renameColumn('forward_ip', 'forward_host');
})
.then(() => {
logger.info('[' + migrate_name + '] proxy_host Table altered');
});
};
/**
* Undo Migrate
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.down = function (knex, Promise) {
logger.warn('[' + migrate_name + '] You can\'t migrate down this one.');
return Promise.resolve(true);
};

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