Compare commits

...

211 Commits

Author SHA1 Message Date
jc21
f3efaae320 Merge pull request #5141 from NginxProxyManager/develop
v2.13.6
2026-01-14 14:30:49 +10:00
jc21
7b3c1fd061 Merge branch 'master' into develop 2026-01-14 13:47:51 +10:00
Jamie Curnow
ee42202348 Bump version 2026-01-14 13:34:17 +10:00
Jamie Curnow
c1ad7788f1 Changed 2fa delete from body to query for code
as per best practices
2026-01-14 13:24:38 +10:00
Jamie Curnow
d33bb02c74 Add missing params to swagger 2026-01-14 12:46:30 +10:00
Jamie Curnow
462c134751 2fa work slight refactor
- use existing access mechanisms for validation
- adds swagger/schema and validation of incoming payload
2026-01-14 11:45:12 +10:00
jc21
b7dfaddbb1 Merge pull request #4970 from zdzichu6969/develop
All checks were successful
Close stale issues and PRs / stale (push) Successful in 33s
Polish Translation Fixes
2026-01-14 07:33:49 +10:00
jc21
11ee4f0820 Merge pull request #4965 from archettitechnology/develop
Update Italian locale message for empty objects
2026-01-14 07:32:07 +10:00
jc21
19970a4220 Merge pull request #5095 from aindriu80/develop
feat: (i18n) Added Irish translation
2026-01-14 07:26:10 +10:00
jc21
59bac3b468 Merge pull request #5005 from NginxProxyManager/dependabot/npm_and_yarn/backend/express-4.22.0
Bump express from 4.21.2 to 4.22.0 in /backend
2026-01-13 23:35:27 +10:00
jc21
48753fb101 Merge pull request #5136 from NginxProxyManager/dependabot/npm_and_yarn/docs/mdast-util-to-hast-13.2.1
Bump mdast-util-to-hast from 13.2.0 to 13.2.1 in /docs
2026-01-13 23:35:13 +10:00
dependabot[bot]
2a3978ae3f Bump mdast-util-to-hast from 13.2.0 to 13.2.1 in /docs
Bumps [mdast-util-to-hast](https://github.com/syntax-tree/mdast-util-to-hast) from 13.2.0 to 13.2.1.
- [Release notes](https://github.com/syntax-tree/mdast-util-to-hast/releases)
- [Commits](https://github.com/syntax-tree/mdast-util-to-hast/compare/13.2.0...13.2.1)

---
updated-dependencies:
- dependency-name: mdast-util-to-hast
  dependency-version: 13.2.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-13 13:28:52 +00:00
dependabot[bot]
4ce5da5930 Bump express from 4.21.2 to 4.22.0 in /backend
Bumps [express](https://github.com/expressjs/express) from 4.21.2 to 4.22.0.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.22.0/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.21.2...4.22.0)

---
updated-dependencies:
- dependency-name: express
  dependency-version: 4.22.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-13 13:26:06 +00:00
jc21
89d3756ee6 Merge pull request #5118 from mobilandi/develop
Add DNS plugin for All-Inkl provider
2026-01-13 23:19:00 +10:00
Jamie Curnow
58c63096e4 Skip color output for vitest in ci 2026-01-13 22:55:19 +10:00
Jamie Curnow
b01a22c393 Fix frontend locale tests after date-fns changed intl formatting
and also attempt to format dates in locale
2026-01-13 22:42:42 +10:00
Jamie Curnow
9c25410331 Fix locale sort not to use sponge 2026-01-13 22:15:54 +10:00
jc21
b3a901bbc5 Merge pull request #5015 from NginxProxyManager/dependabot/npm_and_yarn/backend/jws-3.2.3
Bump jws from 3.2.2 to 3.2.3 in /backend
2026-01-13 15:18:41 +10:00
jc21
3e3396ba9a Update lang-list.json 2026-01-13 15:05:13 +10:00
jc21
3eb493bb8b Merge pull request #5022 from dupsatou/add-dns-plugin-support-he-ddns
Add Hurricane Electric DDNS plugin configuration
2026-01-13 14:53:51 +10:00
jc21
8c8221a352 Merge pull request #5037 from vtj-mizuno/fix-japanese-translate
Fix Japanese translate
2026-01-13 14:53:07 +10:00
jc21
582681e3ff Merge pull request #5080 from bzuro/develop
Change visibility to permission_visibility in report.js
2026-01-13 14:52:45 +10:00
jc21
52fae6d35f Merge pull request #5084 from lacamera/security/CVE-2025-55182
security: bump react to 19.2.3 to fix CVE-2025-55182 (#5020)
2026-01-13 14:50:39 +10:00
jc21
6c0ea835ce Merge branch 'develop' into develop 2026-01-13 14:46:35 +10:00
jc21
fb52655374 Merge pull request #5103 from CamelT0E/develop
Update German locale message from 'German' to 'Deutsch'
2026-01-13 14:43:42 +10:00
Jamie Curnow
336726db8d Backend yarn lock updates 2026-01-13 14:40:10 +10:00
jc21
4a7853163e Merge pull request #5107 from teguh02/develop
feat(i18n): add Bahasa Indonesia translations and help documentation
2026-01-13 14:32:18 +10:00
jc21
b30f8e47e2 Merge pull request #5109 from piotrfx/develop
Add TOTP-based two-factor authentication
2026-01-13 14:30:48 +10:00
jc21
6fa30840be Merge pull request #5114 from Shotz5/develop
Added logging for streams based on port
2026-01-13 14:18:13 +10:00
jc21
05726aaab9 Merge pull request #5119 from manisto/develop
Added support for DNS challenges with Simply.com
2026-01-13 14:14:38 +10:00
jc21
f85bb79f13 Merge pull request #5121 from KalebCheng/feature/certificate-key-type-selection
Add option to select RSA or ECDSA key type when creating certificates
2026-01-13 14:13:22 +10:00
kk.cheng
471b62c7fe Add option to select RSA or ECDSA key type when creating certificates 2026-01-07 19:13:12 +08:00
Gert Rue Brigsted
55a1e0a4e7 Added support for DNS challenges with Simply.com 2026-01-04 21:50:47 +01:00
mobilandi
f25afa3590 Change version constraint for certbot-dns-kas 2026-01-03 23:08:34 +01:00
mobilandi
9211ba6d1a Add DNS plugin for All-Inkl provider 2026-01-03 23:06:25 +01:00
Alex Kitsul
aeb44244a7 Added logging for streams based on port 2025-12-30 21:44:29 -08:00
piotrfx
d2d204ab8e Trigger CI 2025-12-28 12:04:35 +01:00
piotrfx
427afa55b4 Add TOTP-based two-factor authentication
- Add 2FA setup, enable, disable, and backup code management
- Integrate 2FA challenge flow into login process
- Add frontend modal for 2FA configuration
- Support backup codes for account recovery
2025-12-28 11:58:30 +01:00
Teguh Rijanandi
bbe98a639a Add Indonesian locale and help docs 2025-12-27 22:35:17 +07:00
Aindriú Mac Giolla Eoin
f0c0b465d9 Removiving 0x200b - Zero width space 2025-12-20 17:53:05 +00:00
Aindriú Mac Giolla Eoin
6c2f6a9d39 Fixing plural/iolra issue 2025-12-19 11:43:18 +00:00
Aindriú Mac Giolla Eoin
2f6e3ad804 Added Irish translation 2025-12-18 18:21:14 +00:00
Francesco La Camera
5e6ead1eee security: bump react to 19.2.3 to fix CVE-2025-55182 (#5020) 2025-12-15 09:54:18 +01:00
bzuro
da519e72ba Change visibility to permission_visibility in report.js
fix for issue #2014
when even administrator with all_items visibility got 0 proxy hosts in dashboard.
2025-12-14 00:35:22 +01:00
Hajime MIZUNO
b13ebb2247 Fix Japanese translate 2025-12-10 23:28:53 +09:00
dupsatou
6b322582b9 Add Hurricane Electric DDNS plugin configuration
Add support for dns verification using Hurricane Electric DDNS credentials as a more secure way over account root credentials.  More information available here: https://github.com/mafredri/certbot-dns-he-ddns
2025-12-08 09:45:11 -06:00
angioletto
7fe5070337 Merge branch 'NginxProxyManager:develop' into develop 2025-12-06 14:56:52 +01:00
CamelT0E
1b8f1fbb79 Update German locale message from 'German' to 'Deutsch' 2025-12-06 01:30:56 +01:00
dependabot[bot]
4abea1247d Bump jws from 3.2.2 to 3.2.3 in /backend
Bumps [jws](https://github.com/brianloveswords/node-jws) from 3.2.2 to 3.2.3.
- [Release notes](https://github.com/brianloveswords/node-jws/releases)
- [Changelog](https://github.com/auth0/node-jws/blob/master/CHANGELOG.md)
- [Commits](https://github.com/brianloveswords/node-jws/compare/v3.2.2...v3.2.3)

---
updated-dependencies:
- dependency-name: jws
  dependency-version: 3.2.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-04 16:58:07 +00:00
Mateusz Gruszczyński
073ee95e56 change 2025-12-02 12:57:09 +01:00
Jamie Curnow
fec8b3b083 Show full swagger validation errors in tests
All checks were successful
Close stale issues and PRs / stale (push) Successful in 32s
2025-12-02 07:09:54 +10:00
Mateusz Gruszczyński
168078eb40 changes 2025-11-26 10:54:30 +01:00
Mateusz Gruszczyński
2c9f8f4d64 changes 2025-11-26 10:50:41 +01:00
Mateusz Gruszczyński
8403a0c761 changes 2025-11-26 10:42:48 +01:00
jc21
d18c8cf4f1 Merge pull request #4979 from abinas-hdb/develop
All checks were successful
Close stale issues and PRs / stale (push) Successful in 23s
Add Korean Locale
2025-11-26 14:04:31 +10:00
abinas
bf4eab541a Update index.ts
Fix missing 'ko' in index.ts
2025-11-26 11:57:05 +09:00
jc21
f9edcb10e6 Merge pull request #4987 from Bare7a/patch-1
Update Locale README.md to include HelpDoc/index.tsx
2025-11-26 08:35:54 +10:00
jc21
ba43c144f6 Merge branch 'develop' into develop 2025-11-26 08:35:32 +10:00
jc21
896951f6cd Merge pull request #4985 from Bare7a/bg-locale
Add Bulgarian Language Support
2025-11-26 08:33:55 +10:00
jc21
865b566ea6 Merge pull request #4989 from alatalo/develop
Add Glesys certbot plugin
2025-11-26 08:32:03 +10:00
Ville Alatalo
45bc44c6fa Add Glesys certbot plugin 2025-11-25 07:49:24 +02:00
Bare7a
4ff402fff4 Update Locale README.md to include HelpDoc/index.tsx 2025-11-24 18:28:49 +02:00
Bare7a
1c6f54fa3c Changed the port translation 2025-11-24 18:23:40 +02:00
Bare7a
e8ca72fb6a Adds bg inside HelpDoc index.ts file 2025-11-24 18:14:16 +02:00
Bare7a
4712633568 After Translate 2025-11-24 18:07:46 +02:00
Bare7a
a1fb54c394 Before Translating 2025-11-24 18:04:50 +02:00
angioletto
927e57257b Merge branch 'NginxProxyManager:develop' into develop 2025-11-21 17:03:47 +01:00
abinas
e353a66556 Update IntlProvider.tsx 2025-11-22 00:33:27 +09:00
abinas
991bddf891 Add Korean translation 2025-11-22 00:18:36 +09:00
abinas
c076ad145c Add Korean translation 2025-11-22 00:18:19 +09:00
abinas
80cf4406d5 Update Korean language support 2025-11-22 00:15:08 +09:00
abinas
3cb124d5a0 Update Korean language support 2025-11-22 00:14:45 +09:00
abinas
03b0513a24 Add Korean translation 2025-11-22 00:12:33 +09:00
jc21
0528d65317 Merge pull request #4964 from xluyenx/develop
All checks were successful
Close stale issues and PRs / stale (push) Successful in 21s
Correct Vietnam flag
2025-11-20 11:54:55 +10:00
jc21
f9991084fc Merge pull request #4966 from 7heMech/7heMech-patch-1
Increase max propagation seconds to 7200
2025-11-20 11:54:15 +10:00
Mateusz Gruszczyński
56875bba52 pretty :) 2025-11-19 21:23:23 +01:00
Mateusz Gruszczyński
b55f51bd63 fixes1 in pl 2025-11-19 15:10:56 +01:00
7heMech
20e2d5ffb3 Increase max propagation seconds to 7200 2025-11-19 13:00:06 +02:00
Mateusz Gruszczyński
86b7394620 fixes1 2025-11-19 11:01:25 +01:00
Mateusz Gruszczyński
91a1f39c02 fixes1 2025-11-19 10:53:55 +01:00
angioletto
5c114e9db7 Update Italian locale message for empty objects
Wrong translation of line 431
2025-11-19 09:56:05 +01:00
Mateusz Gruszczyński
fec9bffe29 fixes1 2025-11-19 09:13:55 +01:00
Louis Tran's
e3cdc8bb30 Update IntlProvider.tsx 2025-11-19 11:37:20 +07:00
Louis Tran's
ba79eefe5e Merge pull request #1 from xluyenx/xluyenx-patch-1
Update IntlProvider.tsx
2025-11-19 11:30:49 +07:00
Louis Tran's
bb94ce75c1 Update IntlProvider.tsx
Correct Vietnam flag
2025-11-19 11:27:42 +07:00
jc21
847c58b170 Merge pull request #4956 from NginxProxyManager/develop
v2.13.5
2025-11-18 21:13:24 +10:00
jc21
89b8b747e1 Merge branch 'master' into develop
All checks were successful
Close stale issues and PRs / stale (push) Successful in 27s
2025-11-18 19:46:03 +10:00
Jamie Curnow
3231023513 Bump version 2025-11-18 19:42:54 +10:00
Jamie Curnow
dc89635971 Fix up locales, optimised some functions 2025-11-18 19:38:21 +10:00
jc21
cfa98361d1 Merge pull request #4955 from NginxProxyManager/lang-nl
Add Dutch language - resolves #4935
2025-11-18 19:03:48 +10:00
Jelcoo
c2177abe39 Add language to frontend settings & correct some translations 2025-11-18 19:00:00 +10:00
Jelcoo
2c6d614597 Add HelpDoc translations 2025-11-18 18:58:26 +10:00
Jelcoo
484ce8db3c Add Dutch language 2025-11-18 18:57:40 +10:00
jc21
2c11c0c7e2 Merge pull request #4937 from archettitechnology/develop
Add Italian Language Support
2025-11-18 18:50:52 +10:00
jc21
f1039ce2ef Merge pull request #4928 from 7heMech/develop
UI/UX improvements
2025-11-18 18:37:22 +10:00
jc21
d49ff6e7c2 Merge pull request #4934 from zdzichu6969/develop
fix(i18n): replace "Dodaj" with "Nowy" for better Polish grammar and typo Role
2025-11-18 18:30:24 +10:00
jc21
a87f24c9dc Merge pull request #4940 from vsc55/issues_4939
Fix issues #4939, #4938
2025-11-18 18:29:04 +10:00
jc21
decdfec447 Merge branch 'develop' into develop 2025-11-18 18:27:00 +10:00
jc21
32ab3faf57 Merge pull request #4943 from NginxProxyManager/dependabot/npm_and_yarn/backend/js-yaml-4.1.1
Bump js-yaml from 4.1.0 to 4.1.1 in /backend
2025-11-18 18:24:31 +10:00
jc21
c7f999fa7a Merge pull request #4944 from gjssss/patch-1
Fix message for GitHub fork reference in zh.json
2025-11-18 18:24:14 +10:00
jc21
de7d3b0d19 Merge pull request #4950 from dominhhieu1405/develop
Add Vietnamese Support
2025-11-18 18:22:43 +10:00
jc21
2d4b7399c0 Merge pull request #4953 from dodog/develop
Update Slovak language label
2025-11-18 18:20:03 +10:00
Jamie Curnow
316b758455 Tweaks to cypress suite
All checks were successful
Close stale issues and PRs / stale (push) Successful in 20s
2025-11-18 07:21:06 +10:00
Jozef Gaal
890d06c863 Update Slovak language label 2025-11-17 21:07:56 +01:00
dominhhieu1405
81f2aa17d4 Add vietnamese 2025-11-17 22:28:08 +07:00
Jamie Curnow
9b4c34915c Update porkbun certbot plugin
All checks were successful
Close stale issues and PRs / stale (push) Successful in 21s
2025-11-17 08:46:31 +10:00
Javier Pastor
fce569ca21 Modify host.forward-port to avoid line breaks 2025-11-16 01:53:48 +01:00
Json Gao
87ec9c4bdf Fix message for GitHub fork reference in zh.json 2025-11-15 20:09:19 +08:00
dependabot[bot]
2650648d68 Bump js-yaml from 4.1.0 to 4.1.1 in /backend
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.0 to 4.1.1.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.1.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-15 10:40:46 +00:00
7heMech
fdc0c29f28 Improve modals in dark mode via a dark backdrop and shadow. 2025-11-14 15:51:54 +02:00
angioletto
6cae088432 Rename ProxyHost.md to ProxyHosts.md
i think have problem with letter s ahaha
2025-11-14 08:16:41 +01:00
angioletto
9d8c4cc30b Rename DeadHost.md to DeadHosts.md 2025-11-14 08:14:26 +01:00
angioletto
66ebecdb43 Merge branch 'develop' into develop 2025-11-14 08:01:32 +01:00
angioletto
60f3ee03c0 Fix typo in file name from 'indes.ts' to 'index.ts'
typing error
2025-11-14 08:00:30 +01:00
jc21
a4d54a0291 Merge pull request #4932 from kraineff/develop
All checks were successful
Close stale issues and PRs / stale (push) Successful in 20s
Update Russian locale
2025-11-14 16:58:05 +10:00
angioletto
7536b1b1c9 Merge branch 'develop' into develop 2025-11-14 07:19:32 +01:00
angioletto
5288fbd7af Update index.ts 2025-11-14 07:18:14 +01:00
jc21
2c630bbdca Merge branch 'develop' into develop 2025-11-14 15:25:10 +10:00
Javier Pastor
0ec1a09c30 fix issues #4939
add other translations
2025-11-14 00:06:18 +01:00
Jamie Curnow
118c4793e3 Amend locale readme 2025-11-14 08:34:16 +10:00
Jamie Curnow
d7384c568f Fix #4933 when cert may not have domain names 2025-11-14 08:33:42 +10:00
angioletto
0bcfe0bba6 Add Italian language support to lang-list.json 2025-11-13 21:12:52 +01:00
angioletto
74cbfb2c58 Create indes.ts to export HelpDoc modules 2025-11-13 21:12:15 +01:00
angioletto
8ef65caa5a Add Italian documentation for Streams feature 2025-11-13 21:11:19 +01:00
angioletto
bc341c1dff Add RedirectionHosts.md with explanation in Italian 2025-11-13 21:10:36 +01:00
angioletto
5fc9febf1f Update title of ProxyHost.md in Italian 2025-11-13 21:09:40 +01:00
angioletto
b23ceebfd8 Add Italian documentation for ProxyHost 2025-11-13 21:09:23 +01:00
angioletto
c281fc54a1 Add Italian HelpDoc for 404 Host explanation 2025-11-13 21:08:50 +01:00
angioletto
d0f7dc5b48 Add Italian HelpDoc for certificate options 2025-11-13 21:07:26 +01:00
angioletto
fb53df862e Add Italian documentation for Access Lists 2025-11-13 21:03:33 +01:00
angioletto
8d8463ae41 Add Italian language support to HelpDoc 2025-11-13 20:57:52 +01:00
angioletto
8774cfe5f9 Add Italian locale to check-locales 2025-11-13 20:56:42 +01:00
angioletto
4ca5cadd19 Add Italian language support to IntlProvider 2025-11-13 20:55:35 +01:00
angioletto
45a8d50e03 Add IT Translation 2025-11-13 20:52:42 +01:00
7heMech
960d4bfe6f Revert change which should have no effect on theory 2025-11-13 14:51:00 +02:00
7heMech
8c3c964c52 Fix page offset 2025-11-13 14:27:55 +02:00
7heMech
afd6134a3e Get rid of logo flicker and improve LCP 2025-11-13 14:04:37 +02:00
Alexey Krainev
9b2d60e67b Update Russian locale 2025-11-13 16:58:04 +05:00
7heMech
9807e25d45 Remove unused import 2025-11-13 12:49:48 +02:00
7heMech
824c895f52 Remove cn where not needed 2025-11-13 12:47:01 +02:00
7heMech
7f9b9dfea4 Fix for dropdown menus being clipped by table-responsive containers. 2025-11-13 12:06:36 +02:00
Mateusz Gruszczyński
d848ba9f65 Fixed typo: corrected 'role' to proper Polish declension 'rola' and 'nowy' 2025-11-13 09:05:07 +01:00
Mateusz Gruszczyński
47db5c9aa6 Fixed typo: corrected 'role' to proper Polish declension 'rola' 2025-11-13 08:57:30 +01:00
Jamie Curnow
79a9653b26 Remove the compiled lang files, compile on dev server and when building in ci
All checks were successful
Close stale issues and PRs / stale (push) Successful in 23s
This avoids confusion for new translators
2025-11-13 14:21:32 +10:00
Jamie Curnow
e5aae1f365 Fix openapi schema format 2025-11-13 11:51:13 +10:00
Jamie Curnow
8959190d32 Change docker ci expose format for docker 28 :/ 2025-11-13 11:37:58 +10:00
Jamie Curnow
7e875eb27a Change docker ci expose format for docker 28 :/ 2025-11-13 11:35:11 +10:00
Jamie Curnow
cf7306e766 Tweaks to showing new version available
- Added frontend translation for english
- Moved frontend api logic to hook and backend api space
- Added swagger schema for the new api endpoint
- Moved backend logic to its own internal file
- Added user agent header to github api check
- Added cypress integration test for version check api
- Added a memory cache item from github check to avoid hitting it too
  much
2025-11-13 11:20:31 +10:00
7heMech
1c442dcce6 True mobile layout with responsive table rows (sticky header) 2025-11-13 02:44:24 +02:00
7heMech
dadd10f89b Fixed my troubles with text wrap 2025-11-13 02:21:58 +02:00
jc21
8838dabe8a Merge pull request #4906 from sopex/develop
Available upgrade notification
2025-11-13 10:15:33 +10:00
7heMech
75c012b558 Fix linter error 2025-11-13 01:58:48 +02:00
7heMech
9be1381ffe Uhhh, I didn't like the Standard User lol 2025-11-13 01:46:39 +02:00
7heMech
f40fe56572 Add new section with theme and locale pickers. 2025-11-13 01:40:34 +02:00
Konstantinos Spartalis
b4fd242eb7 remove 1 2025-11-13 00:48:49 +02:00
7heMech
911476f82f Delay before close for smooth feel. 2025-11-13 00:46:36 +02:00
7heMech
963125f963 Space scandal retified (hopefully) 2025-11-13 00:45:07 +02:00
7heMech
e86a34f2f3 Close menu after navigation. 2025-11-13 00:30:45 +02:00
jc21
6ce9567e48 Merge pull request #4816 from fhennig42/azure-dns
All checks were successful
Close stale issues and PRs / stale (push) Successful in 22s
Bump certbot-azure-dns version
2025-11-13 07:13:11 +10:00
jc21
f02145c5ef Merge pull request #4925 from NginxProxyManager/develop
v2.13.4
2025-11-13 06:57:28 +10:00
7heMech
66fa08fd8e Add profile back to main app on mobile 2025-11-12 18:12:58 +02:00
7heMech
d783cc3b90 Remove unused styles 2025-11-12 17:58:54 +02:00
7heMech
17cc75fe7d Fix language and theme selectors on mobile and desktop 2025-11-12 17:43:46 +02:00
Konstantinos Spartalis
15394c6532 trigger Jenkins that failed due to internet connection problems 2025-11-12 15:50:11 +02:00
Konstantinos Spartalis
2d6252d75d https.get 2025-11-12 15:45:59 +02:00
jc21
adee0e39de Merge branch 'master' into develop 2025-11-12 23:02:28 +10:00
Jamie Curnow
5dde98cf3e Updates to polish locale after running through automated scripts 2025-11-12 23:01:40 +10:00
jc21
c41451618e Merge pull request #4924 from zdzichu6969/develop
Add Polish locale
2025-11-12 22:59:23 +10:00
jc21
1a3d45f6bc Merge branch 'develop' into develop 2025-11-12 22:14:28 +10:00
jc21
2ea54975b6 Merge pull request #4922 from NginxProxyManager/dodog-slovak
Add Slovak language by @dodog in #4911
2025-11-12 22:13:05 +10:00
Mateusz Gruszczyński
0373017a9f Add Polish locale 2025-11-12 13:10:29 +01:00
Florian Hennig
b043e70fc0 add azure-mgmt-dns fix version as dependency 2025-11-12 13:00:34 +01:00
Jamie Curnow
2b5182d339 Add Slovak language by @dodog in #4911 2025-11-12 21:49:04 +10:00
jc21
3c5ff81a54 Merge pull request #4910 from 7heMech/develop
Add scheme back in destination
2025-11-12 20:48:56 +10:00
jc21
8aa46c1f40 Merge pull request #4921 from NginxProxyManager/Firfr-chinese
Add Chinese language 添加中文
2025-11-12 20:47:15 +10:00
Jamie Curnow
b26db50ae7 Adds cn to check locales script 2025-11-12 20:26:22 +10:00
firfe
d66bb2104a Add the new translation for "redirection-host.forward-http-code". 2025-11-12 20:23:36 +10:00
firfe
8e900dbc92 Add Chinese HelpDoc 2025-11-12 20:23:34 +10:00
firfe
66aac3eb3e Add Chinese 中文 2025-11-12 20:22:57 +10:00
jc21
221c3eddbc Merge pull request #4919 from lastsamurai26/develop
Fix: German grammatical change
2025-11-12 20:16:58 +10:00
Jamie Curnow
8460b28597 Bump version 2025-11-12 20:13:18 +10:00
Frank
0344bb3c19 fix: Grammatical change
fix: Grammatical change
2025-11-12 10:47:53 +01:00
Frank
1a36bdce76 fix: Grammatical change
fix: Grammatical change
2025-11-12 10:47:51 +01:00
Jamie Curnow
06d7db43f7 Fix Russion locale, compiled file was comitted without a source file 2025-11-12 18:59:37 +10:00
jc21
4557244744 Merge pull request #4870 from kraineff/develop
Add Russian Support
2025-11-12 18:51:43 +10:00
jc21
f649288098 Merge branch 'develop' into develop 2025-11-12 18:39:05 +10:00
jc21
28df6db52b Merge pull request #4848 from Oka-Tak/develop
Add Japanese language support and translations
2025-11-12 18:36:18 +10:00
jc21
eee749652c Merge pull request #4917 from lastsamurai26/develop
Fix: wrong translate and adding missing translations
2025-11-12 18:13:08 +10:00
jc21
f6aa25b9b3 Merge branch 'develop' into develop 2025-11-12 18:12:10 +10:00
Frank
40db26b686 Merge branch 'NginxProxyManager:develop' into develop 2025-11-12 08:06:36 +01:00
Frank
f36d4e6906 Fix: CustomCertificateModal Wrong displayname
Fix: https://github.com/NginxProxyManager/nginx-proxy-manager/issues/4912 Wrong Locale for Custom
2025-11-12 07:47:06 +01:00
Frank
86c7cbddab Add certificate renewal message in German locale
Fix: add missing translation for renew certificates
2025-11-12 07:34:44 +01:00
Frank
e52975bf6c Translate 'Renew Certificate' to German
Fix: add missing translation for renew certificates
2025-11-12 07:34:42 +01:00
Frank
ff792f76af Add translation for 'Renew Certificate' in de.json
Fix: Add missing translation für renew Certificate
2025-11-12 07:32:34 +01:00
Jamie Curnow
711f312b71 Fix up language inconsistenties 2025-11-12 16:30:22 +10:00
Jamie Curnow
9f0f89ff03 Fix wrong translation for EN 2025-11-12 15:13:14 +10:00
Konstantinos Spartalis
87eef10ff8 remove useCallback logic 2025-11-11 18:30:23 +02:00
Konstantinos Spartalis
dc03ad8239 minimal changes 2025-11-11 17:42:46 +02:00
7heMech
441a7262cd Add scheme back in destination 2025-11-11 12:54:01 +00:00
Konstantinos Spartalis
ae5faa75fa backend test 2025-11-11 10:35:00 +02:00
jc21
2578105f86 Merge pull request #4907 from NginxProxyManager/develop
v2.13.3
2025-11-11 16:54:38 +10:00
Konstantinos Spartalis
b6dbb68ef3 Update SiteFooter.tsx 2025-11-10 20:42:52 +02:00
Konstantinos Spartalis
b434bba12f remove hardcoded version number 2025-11-10 20:37:25 +02:00
Konstantinos Spartalis
f1d7203212 v2 2025-11-10 19:57:55 +02:00
Konstantinos Spartalis
990ba28831 Update SiteFooter.tsx 2025-11-10 19:43:38 +02:00
Alexey Krainev
5aa56c63d4 Fixes & New Strings 2025-11-08 17:15:24 +05:00
Alexey Krainev
8fdb6091f3 More strings 2025-11-08 15:51:39 +05:00
Alexey Krainev
58182fcbdf Add Russian case 2025-11-08 15:08:08 +05:00
Alexey Krainev
b3b1e94b8c Add Russian Support 2025-11-08 15:02:05 +05:00
Takahisa-Okawa
9de40f067b Add Japanese language support and translations
Co-authored-by: kz2870 <kz2870@users.noreply.github.com>
2025-11-05 22:25:15 +09:00
Florian Hennig
a85b5f664f Bump version after rebase 2025-11-04 20:03:09 +01:00
191 changed files with 13110 additions and 2662 deletions

View File

@@ -1 +1 @@
2.13.3 2.13.6

View File

@@ -1,7 +1,7 @@
<p align="center"> <p align="center">
<img src="https://nginxproxymanager.com/github.png"> <img src="https://nginxproxymanager.com/github.png">
<br><br> <br><br>
<img src="https://img.shields.io/badge/version-2.13.3-green.svg?style=for-the-badge"> <img src="https://img.shields.io/badge/version-2.13.6-green.svg?style=for-the-badge">
<a href="https://hub.docker.com/repository/docker/jc21/nginx-proxy-manager"> <a href="https://hub.docker.com/repository/docker/jc21/nginx-proxy-manager">
<img src="https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge"> <img src="https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge">
</a> </a>

View File

@@ -26,8 +26,8 @@
"azure": { "azure": {
"name": "Azure", "name": "Azure",
"package_name": "certbot-dns-azure", "package_name": "certbot-dns-azure",
"version": "~=1.2.0", "version": "~=2.6.1",
"dependencies": "", "dependencies": "azure-mgmt-dns==8.2.0",
"credentials": "# This plugin supported API authentication using either Service Principals or utilizing a Managed Identity assigned to the virtual machine.\n# Regardless which authentication method used, the identity will need the “DNS Zone Contributor” role assigned to it.\n# As multiple Azure DNS Zones in multiple resource groups can exist, the config file needs a mapping of zone to resource group ID. Multiple zones -> ID mappings can be listed by using the key dns_azure_zoneX where X is a unique number. At least 1 zone mapping is required.\n\n# Using a service principal (option 1)\ndns_azure_sp_client_id = 912ce44a-0156-4669-ae22-c16a17d34ca5\ndns_azure_sp_client_secret = E-xqXU83Y-jzTI6xe9fs2YC~mck3ZzUih9\ndns_azure_tenant_id = ed1090f3-ab18-4b12-816c-599af8a88cf7\n\n# Using used assigned MSI (option 2)\n# dns_azure_msi_client_id = 912ce44a-0156-4669-ae22-c16a17d34ca5\n\n# Using system assigned MSI (option 3)\n# dns_azure_msi_system_assigned = true\n\n# Zones (at least one always required)\ndns_azure_zone1 = example.com:/subscriptions/c135abce-d87d-48df-936c-15596c6968a5/resourceGroups/dns1\ndns_azure_zone2 = example.org:/subscriptions/99800903-fb14-4992-9aff-12eaf2744622/resourceGroups/dns2", "credentials": "# This plugin supported API authentication using either Service Principals or utilizing a Managed Identity assigned to the virtual machine.\n# Regardless which authentication method used, the identity will need the “DNS Zone Contributor” role assigned to it.\n# As multiple Azure DNS Zones in multiple resource groups can exist, the config file needs a mapping of zone to resource group ID. Multiple zones -> ID mappings can be listed by using the key dns_azure_zoneX where X is a unique number. At least 1 zone mapping is required.\n\n# Using a service principal (option 1)\ndns_azure_sp_client_id = 912ce44a-0156-4669-ae22-c16a17d34ca5\ndns_azure_sp_client_secret = E-xqXU83Y-jzTI6xe9fs2YC~mck3ZzUih9\ndns_azure_tenant_id = ed1090f3-ab18-4b12-816c-599af8a88cf7\n\n# Using used assigned MSI (option 2)\n# dns_azure_msi_client_id = 912ce44a-0156-4669-ae22-c16a17d34ca5\n\n# Using system assigned MSI (option 3)\n# dns_azure_msi_system_assigned = true\n\n# Zones (at least one always required)\ndns_azure_zone1 = example.com:/subscriptions/c135abce-d87d-48df-936c-15596c6968a5/resourceGroups/dns1\ndns_azure_zone2 = example.org:/subscriptions/99800903-fb14-4992-9aff-12eaf2744622/resourceGroups/dns2",
"full_plugin_name": "dns-azure" "full_plugin_name": "dns-azure"
}, },
@@ -255,6 +255,14 @@
"credentials": "dns_gcore_apitoken = 0123456789abcdef0123456789abcdef01234567", "credentials": "dns_gcore_apitoken = 0123456789abcdef0123456789abcdef01234567",
"full_plugin_name": "dns-gcore" "full_plugin_name": "dns-gcore"
}, },
"glesys": {
"name": "Glesys",
"package_name": "certbot-dns-glesys",
"version": "~=2.1.0",
"dependencies": "",
"credentials": "dns_glesys_user = CL00000\ndns_glesys_password = apikeyvalue",
"full_plugin_name": "dns-glesys"
},
"godaddy": { "godaddy": {
"name": "GoDaddy", "name": "GoDaddy",
"package_name": "certbot-dns-godaddy", "package_name": "certbot-dns-godaddy",
@@ -287,6 +295,14 @@
"credentials": "dns_he_user = Me\ndns_he_pass = my HE password", "credentials": "dns_he_user = Me\ndns_he_pass = my HE password",
"full_plugin_name": "dns-he" "full_plugin_name": "dns-he"
}, },
"he-ddns": {
"name": "Hurricane Electric - DDNS",
"package_name": "certbot-dns-he-ddns",
"version": "~=0.1.0",
"dependencies": "",
"credentials": "dns_he_ddns_password = verysecurepassword",
"full_plugin_name": "dns-he-ddns"
},
"hetzner": { "hetzner": {
"name": "Hetzner", "name": "Hetzner",
"package_name": "certbot-dns-hetzner", "package_name": "certbot-dns-hetzner",
@@ -366,6 +382,14 @@
"dependencies": "", "dependencies": "",
"credentials": "dns_joker_username = <Dynamic DNS Authentication Username>\ndns_joker_password = <Dynamic DNS Authentication Password>\ndns_joker_domain = <Dynamic DNS Domain>", "credentials": "dns_joker_username = <Dynamic DNS Authentication Username>\ndns_joker_password = <Dynamic DNS Authentication Password>\ndns_joker_domain = <Dynamic DNS Domain>",
"full_plugin_name": "dns-joker" "full_plugin_name": "dns-joker"
},
"kas": {
"name": "All-Inkl",
"package_name": "certbot-dns-kas",
"version": "~=0.1.1",
"dependencies": "kasserver",
"credentials": "dns_kas_user = your_kas_user\ndns_kas_password = your_kas_password",
"full_plugin_name": "dns-kas"
}, },
"leaseweb": { "leaseweb": {
"name": "LeaseWeb", "name": "LeaseWeb",
@@ -482,7 +506,7 @@
"porkbun": { "porkbun": {
"name": "Porkbun", "name": "Porkbun",
"package_name": "certbot-dns-porkbun", "package_name": "certbot-dns-porkbun",
"version": "~=0.9", "version": "~=0.11.0",
"dependencies": "", "dependencies": "",
"credentials": "dns_porkbun_key=your-porkbun-api-key\ndns_porkbun_secret=your-porkbun-api-secret", "credentials": "dns_porkbun_key=your-porkbun-api-key\ndns_porkbun_secret=your-porkbun-api-secret",
"full_plugin_name": "dns-porkbun" "full_plugin_name": "dns-porkbun"
@@ -527,6 +551,14 @@
"credentials": "[default]\naws_access_key_id=AKIAIOSFODNN7EXAMPLE\naws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", "credentials": "[default]\naws_access_key_id=AKIAIOSFODNN7EXAMPLE\naws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"full_plugin_name": "dns-route53" "full_plugin_name": "dns-route53"
}, },
"simply": {
"name": "Simply",
"package_name": "certbot-dns-simply",
"version": "~=0.1.2",
"dependencies": "",
"credentials": "dns_simply_account_name = UExxxxxx\ndns_simply_api_key = DsHJdsjh2812872sahj",
"full_plugin_name": "dns-simply"
},
"spaceship": { "spaceship": {
"name": "Spaceship", "name": "Spaceship",
"package_name": "certbot-dns-spaceship", "package_name": "certbot-dns-spaceship",

288
backend/internal/2fa.js Normal file
View File

@@ -0,0 +1,288 @@
import crypto from "node:crypto";
import bcrypt from "bcrypt";
import { authenticator } from "otplib";
import errs from "../lib/error.js";
import authModel from "../models/auth.js";
import internalUser from "./user.js";
const APP_NAME = "Nginx Proxy Manager";
const BACKUP_CODE_COUNT = 8;
/**
* Generate backup codes
* @returns {Promise<{plain: string[], hashed: string[]}>}
*/
const generateBackupCodes = async () => {
const plain = [];
const hashed = [];
for (let i = 0; i < BACKUP_CODE_COUNT; i++) {
const code = crypto.randomBytes(4).toString("hex").toUpperCase();
plain.push(code);
const hash = await bcrypt.hash(code, 10);
hashed.push(hash);
}
return { plain, hashed };
};
const internal2fa = {
/**
* Check if user has 2FA enabled
* @param {number} userId
* @returns {Promise<boolean>}
*/
isEnabled: async (userId) => {
const auth = await internal2fa.getUserPasswordAuth(userId);
return auth?.meta?.totp_enabled === true;
},
/**
* Get 2FA status for user
* @param {Access} access
* @param {number} userId
* @returns {Promise<{enabled: boolean, backup_codes_remaining: number}>}
*/
getStatus: async (access, userId) => {
await access.can("users:password", userId);
await internalUser.get(access, { id: userId });
const auth = await internal2fa.getUserPasswordAuth(userId);
const enabled = auth?.meta?.totp_enabled === true;
let backup_codes_remaining = 0;
if (enabled) {
const backupCodes = auth.meta.backup_codes || [];
backup_codes_remaining = backupCodes.length;
}
return {
enabled,
backup_codes_remaining,
};
},
/**
* Start 2FA setup - store pending secret
*
* @param {Access} access
* @param {number} userId
* @returns {Promise<{secret: string, otpauth_url: string}>}
*/
startSetup: async (access, userId) => {
await access.can("users:password", userId);
const user = await internalUser.get(access, { id: userId });
const secret = authenticator.generateSecret();
const otpauth_url = authenticator.keyuri(user.email, APP_NAME, secret);
const auth = await internal2fa.getUserPasswordAuth(userId);
// ensure user isn't already setup for 2fa
const enabled = auth?.meta?.totp_enabled === true;
if (enabled) {
throw new errs.ValidationError("2FA is already enabled");
}
const meta = auth.meta || {};
meta.totp_pending_secret = secret;
await authModel.query()
.where("id", auth.id)
.andWhere("user_id", userId)
.andWhere("type", "password")
.patch({ meta });
return { secret, otpauth_url };
},
/**
* Enable 2FA after verifying code
*
* @param {Access} access
* @param {number} userId
* @param {string} code
* @returns {Promise<{backup_codes: string[]}>}
*/
enable: async (access, userId, code) => {
await access.can("users:password", userId);
await internalUser.get(access, { id: userId });
const auth = await internal2fa.getUserPasswordAuth(userId);
const secret = auth?.meta?.totp_pending_secret || false;
if (!secret) {
throw new errs.ValidationError("No pending 2FA setup found");
}
const valid = authenticator.verify({ token: code, secret });
if (!valid) {
throw new errs.ValidationError("Invalid verification code");
}
const { plain, hashed } = await generateBackupCodes();
const meta = {
...auth.meta,
totp_secret: secret,
totp_enabled: true,
totp_enabled_at: new Date().toISOString(),
backup_codes: hashed,
};
delete meta.totp_pending_secret;
await authModel
.query()
.where("id", auth.id)
.andWhere("user_id", userId)
.andWhere("type", "password")
.patch({ meta });
return { backup_codes: plain };
},
/**
* Disable 2FA
*
* @param {Access} access
* @param {number} userId
* @param {string} code
* @returns {Promise<void>}
*/
disable: async (access, userId, code) => {
await access.can("users:password", userId);
await internalUser.get(access, { id: userId });
const auth = await internal2fa.getUserPasswordAuth(userId);
const enabled = auth?.meta?.totp_enabled === true;
if (!enabled) {
throw new errs.ValidationError("2FA is not enabled");
}
const valid = authenticator.verify({
token: code,
secret: auth.meta.totp_secret,
});
if (!valid) {
throw new errs.AuthError("Invalid verification code");
}
const meta = { ...auth.meta };
delete meta.totp_secret;
delete meta.totp_enabled;
delete meta.totp_enabled_at;
delete meta.backup_codes;
await authModel
.query()
.where("id", auth.id)
.andWhere("user_id", userId)
.andWhere("type", "password")
.patch({ meta });
},
/**
* Verify 2FA code for login
*
* @param {number} userId
* @param {string} token
* @returns {Promise<boolean>}
*/
verifyForLogin: async (userId, token) => {
const auth = await internal2fa.getUserPasswordAuth(userId);
const secret = auth?.meta?.totp_secret || false;
if (!secret) {
return false;
}
// Try TOTP code first
const valid = authenticator.verify({
token,
secret,
});
if (valid) {
return true;
}
// Try backup codes
const backupCodes = auth?.meta?.backup_codes || [];
for (let i = 0; i < backupCodes.length; i++) {
const match = await bcrypt.compare(code.toUpperCase(), backupCodes[i]);
if (match) {
// Remove used backup code
const updatedCodes = [...backupCodes];
updatedCodes.splice(i, 1);
const meta = { ...auth.meta, backup_codes: updatedCodes };
await authModel
.query()
.where("id", auth.id)
.andWhere("user_id", userId)
.andWhere("type", "password")
.patch({ meta });
return true;
}
}
return false;
},
/**
* Regenerate backup codes
*
* @param {Access} access
* @param {number} userId
* @param {string} token
* @returns {Promise<{backup_codes: string[]}>}
*/
regenerateBackupCodes: async (access, userId, token) => {
await access.can("users:password", userId);
await internalUser.get(access, { id: userId });
const auth = await internal2fa.getUserPasswordAuth(userId);
const enabled = auth?.meta?.totp_enabled === true;
const secret = auth?.meta?.totp_secret || false;
if (!enabled) {
throw new errs.ValidationError("2FA is not enabled");
}
if (!secret) {
throw new errs.ValidationError("No 2FA secret found");
}
const valid = authenticator.verify({
token,
secret,
});
if (!valid) {
throw new errs.ValidationError("Invalid verification code");
}
const { plain, hashed } = await generateBackupCodes();
const meta = { ...auth.meta, backup_codes: hashed };
await authModel
.query()
.where("id", auth.id)
.andWhere("user_id", userId)
.andWhere("type", "password")
.patch({ meta });
return { backup_codes: plain };
},
getUserPasswordAuth: async (userId) => {
const auth = await authModel
.query()
.where("user_id", userId)
.andWhere("type", "password")
.first();
if (!auth) {
throw new errs.ItemNotFoundError("Auth not found");
}
return auth;
},
};
export default internal2fa;

View File

@@ -798,6 +798,11 @@ const internalCertificate = {
certificate.domain_names.join(","), certificate.domain_names.join(","),
]; ];
// Add key-type parameter if specified
if (certificate.meta?.key_type) {
args.push("--key-type", certificate.meta.key_type);
}
const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id); const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id);
args.push(...adds.args); args.push(...adds.args);
@@ -858,6 +863,11 @@ const internalCertificate = {
); );
} }
// Add key-type parameter if specified
if (certificate.meta?.key_type) {
args.push("--key-type", certificate.meta.key_type);
}
const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider); const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider);
args.push(...adds.args); args.push(...adds.args);
@@ -938,6 +948,11 @@ const internalCertificate = {
"--disable-hook-validation", "--disable-hook-validation",
]; ];
// Add key-type parameter if specified
if (certificate.meta?.key_type) {
args.push("--key-type", certificate.meta.key_type);
}
const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider); const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider);
args.push(...adds.args); args.push(...adds.args);
@@ -979,6 +994,11 @@ const internalCertificate = {
"--no-random-sleep-on-renew", "--no-random-sleep-on-renew",
]; ];
// Add key-type parameter if specified
if (certificate.meta?.key_type) {
args.push("--key-type", certificate.meta.key_type);
}
const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider); const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider);
args.push(...adds.args); args.push(...adds.args);

View File

@@ -0,0 +1,84 @@
import https from "node:https";
import { ProxyAgent } from "proxy-agent";
import { debug, remoteVersion as logger } from "../logger.js";
import pjson from "../package.json" with { type: "json" };
const VERSION_URL = "https://api.github.com/repos/NginxProxyManager/nginx-proxy-manager/releases/latest";
const internalRemoteVersion = {
cache_timeout: 1000 * 60 * 15, // 15 minutes
last_result: null,
last_fetch_time: null,
/**
* Fetch the latest version info, using a cached result if within the cache timeout period.
* @return {Promise<{current: string, latest: string, update_available: boolean}>} Version info
*/
get: async () => {
if (
!internalRemoteVersion.last_result ||
!internalRemoteVersion.last_fetch_time ||
Date.now() - internalRemoteVersion.last_fetch_time > internalRemoteVersion.cache_timeout
) {
const raw = await internalRemoteVersion.fetchUrl(VERSION_URL);
const data = JSON.parse(raw);
internalRemoteVersion.last_result = data;
internalRemoteVersion.last_fetch_time = Date.now();
} else {
debug(logger, "Using cached remote version result");
}
const latestVersion = internalRemoteVersion.last_result.tag_name;
const version = pjson.version.split("-").shift().split(".");
const currentVersion = `v${version[0]}.${version[1]}.${version[2]}`;
return {
current: currentVersion,
latest: latestVersion,
update_available: internalRemoteVersion.compareVersions(currentVersion, latestVersion),
};
},
fetchUrl: (url) => {
const agent = new ProxyAgent();
const headers = {
"User-Agent": `NginxProxyManager v${pjson.version}`,
};
return new Promise((resolve, reject) => {
logger.info(`Fetching ${url}`);
return https
.get(url, { agent, headers }, (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);
});
});
},
compareVersions: (current, latest) => {
const cleanCurrent = current.replace(/^v/, "");
const cleanLatest = latest.replace(/^v/, "");
const currentParts = cleanCurrent.split(".").map(Number);
const latestParts = cleanLatest.split(".").map(Number);
for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
const curr = currentParts[i] || 0;
const lat = latestParts[i] || 0;
if (lat > curr) return true;
if (lat < curr) return false;
}
return false;
},
};
export default internalRemoteVersion;

View File

@@ -15,10 +15,10 @@ const internalReport = {
const userId = access.token.getUserId(1); const userId = access.token.getUserId(1);
const promises = [ const promises = [
internalProxyHost.getCount(userId, access_data.visibility), internalProxyHost.getCount(userId, access_data.permission_visibility),
internalRedirectionHost.getCount(userId, access_data.visibility), internalRedirectionHost.getCount(userId, access_data.permission_visibility),
internalStream.getCount(userId, access_data.visibility), internalStream.getCount(userId, access_data.permission_visibility),
internalDeadHost.getCount(userId, access_data.visibility), internalDeadHost.getCount(userId, access_data.permission_visibility),
]; ];
return Promise.all(promises); return Promise.all(promises);

View File

@@ -4,9 +4,12 @@ import { parseDatePeriod } from "../lib/helpers.js";
import authModel from "../models/auth.js"; import authModel from "../models/auth.js";
import TokenModel from "../models/token.js"; import TokenModel from "../models/token.js";
import userModel from "../models/user.js"; import userModel from "../models/user.js";
import twoFactor from "./2fa.js";
const ERROR_MESSAGE_INVALID_AUTH = "Invalid email or password"; const ERROR_MESSAGE_INVALID_AUTH = "Invalid email or password";
const ERROR_MESSAGE_INVALID_AUTH_I18N = "error.invalid-auth"; const ERROR_MESSAGE_INVALID_AUTH_I18N = "error.invalid-auth";
const ERROR_MESSAGE_INVALID_2FA = "Invalid verification code";
const ERROR_MESSAGE_INVALID_2FA_I18N = "error.invalid-2fa";
export default { export default {
/** /**
@@ -59,6 +62,25 @@ export default {
throw new errs.AuthError(`Invalid scope: ${data.scope}`); throw new errs.AuthError(`Invalid scope: ${data.scope}`);
} }
// Check if 2FA is enabled
const has2FA = await twoFactor.isEnabled(user.id);
if (has2FA) {
// Return challenge token instead of full token
const challengeToken = await Token.create({
iss: issuer || "api",
attrs: {
id: user.id,
},
scope: ["2fa-challenge"],
expiresIn: "5m",
});
return {
requires_2fa: true,
challenge_token: challengeToken.token,
};
}
// Create a moment of the expiry expression // Create a moment of the expiry expression
const expiry = parseDatePeriod(data.expiry); const expiry = parseDatePeriod(data.expiry);
if (expiry === null) { if (expiry === null) {
@@ -129,6 +151,65 @@ export default {
throw new error.AssertionFailedError("Existing token contained invalid user data"); throw new error.AssertionFailedError("Existing token contained invalid user data");
}, },
/**
* Verify 2FA code and return full token
* @param {string} challengeToken
* @param {string} code
* @param {string} [expiry]
* @returns {Promise}
*/
verify2FA: async (challengeToken, code, expiry) => {
const Token = TokenModel();
const tokenExpiry = expiry || "1d";
// Verify challenge token
let tokenData;
try {
tokenData = await Token.load(challengeToken);
} catch {
throw new errs.AuthError("Invalid or expired challenge token");
}
// Check scope
if (!tokenData.scope || tokenData.scope[0] !== "2fa-challenge") {
throw new errs.AuthError("Invalid challenge token");
}
const userId = tokenData.attrs?.id;
if (!userId) {
throw new errs.AuthError("Invalid challenge token");
}
// Verify 2FA code
const valid = await twoFactor.verifyForLogin(userId, code);
if (!valid) {
throw new errs.AuthError(
ERROR_MESSAGE_INVALID_2FA,
ERROR_MESSAGE_INVALID_2FA_I18N,
);
}
// Create full token
const expiryDate = parseDatePeriod(tokenExpiry);
if (expiryDate === null) {
throw new errs.AuthError(`Invalid expiry time: ${tokenExpiry}`);
}
const signed = await Token.create({
iss: "api",
attrs: {
id: userId,
},
scope: ["user"],
expiresIn: tokenExpiry,
});
return {
token: signed.token,
expires: expiryDate.toISOString(),
};
},
/** /**
* @param {Object} user * @param {Object} user
* @returns {Promise} * @returns {Promise}

View File

@@ -15,6 +15,7 @@ const certbot = new signale.Signale({ scope: "Certbot ", ...opts });
const importer = new signale.Signale({ scope: "Importer ", ...opts }); const importer = new signale.Signale({ scope: "Importer ", ...opts });
const setup = new signale.Signale({ scope: "Setup ", ...opts }); const setup = new signale.Signale({ scope: "Setup ", ...opts });
const ipRanges = new signale.Signale({ scope: "IP Ranges", ...opts }); const ipRanges = new signale.Signale({ scope: "IP Ranges", ...opts });
const remoteVersion = new signale.Signale({ scope: "Remote Version", ...opts });
const debug = (logger, ...args) => { const debug = (logger, ...args) => {
if (isDebugMode()) { if (isDebugMode()) {
@@ -22,4 +23,4 @@ const debug = (logger, ...args) => {
} }
}; };
export { debug, global, migrate, express, access, nginx, ssl, certbot, importer, setup, ipRanges }; export { debug, global, migrate, express, access, nginx, ssl, certbot, importer, setup, ipRanges, remoteVersion };

View File

@@ -19,7 +19,7 @@
"bcrypt": "^5.0.0", "bcrypt": "^5.0.0",
"body-parser": "^1.20.3", "body-parser": "^1.20.3",
"compression": "^1.7.4", "compression": "^1.7.4",
"express": "^4.20.0", "express": "^4.22.0",
"express-fileupload": "^1.5.2", "express-fileupload": "^1.5.2",
"gravatar": "^1.8.2", "gravatar": "^1.8.2",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
@@ -30,6 +30,7 @@
"mysql2": "^3.15.3", "mysql2": "^3.15.3",
"node-rsa": "^1.1.1", "node-rsa": "^1.1.1",
"objection": "3.0.1", "objection": "3.0.1",
"otplib": "^12.0.1",
"path": "^0.12.7", "path": "^0.12.7",
"pg": "^8.16.3", "pg": "^8.16.3",
"proxy-agent": "^6.5.0", "proxy-agent": "^6.5.0",

View File

@@ -14,6 +14,7 @@ import schemaRoutes from "./schema.js";
import settingsRoutes from "./settings.js"; import settingsRoutes from "./settings.js";
import tokensRoutes from "./tokens.js"; import tokensRoutes from "./tokens.js";
import usersRoutes from "./users.js"; import usersRoutes from "./users.js";
import versionRoutes from "./version.js";
const router = express.Router({ const router = express.Router({
caseSensitive: true, caseSensitive: true,
@@ -46,6 +47,7 @@ router.use("/users", usersRoutes);
router.use("/audit-log", auditLogRoutes); router.use("/audit-log", auditLogRoutes);
router.use("/reports", reportsRoutes); router.use("/reports", reportsRoutes);
router.use("/settings", settingsRoutes); router.use("/settings", settingsRoutes);
router.use("/version", versionRoutes);
router.use("/nginx/proxy-hosts", proxyHostsRoutes); router.use("/nginx/proxy-hosts", proxyHostsRoutes);
router.use("/nginx/redirection-hosts", redirectionHostsRoutes); router.use("/nginx/redirection-hosts", redirectionHostsRoutes);
router.use("/nginx/dead-hosts", deadHostsRoutes); router.use("/nginx/dead-hosts", deadHostsRoutes);

View File

@@ -53,4 +53,26 @@ router
} }
}); });
router
.route("/2fa")
.options((_, res) => {
res.sendStatus(204);
})
/**
* POST /tokens/2fa
*
* Verify 2FA code and get full token
*/
.post(async (req, res, next) => {
try {
const { challenge_token, code } = await apiValidator(getValidationSchema("/tokens/2fa", "post"), req.body);
const result = await internalToken.verify2FA(challenge_token, code);
res.status(200).send(result);
} catch (err) {
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});
export default router; export default router;

View File

@@ -1,4 +1,5 @@
import express from "express"; import express from "express";
import internal2FA from "../internal/2fa.js";
import internalUser from "../internal/user.js"; import internalUser from "../internal/user.js";
import Access from "../lib/access.js"; import Access from "../lib/access.js";
import { isCI } from "../lib/config.js"; import { isCI } from "../lib/config.js";
@@ -325,4 +326,130 @@ router
} }
}); });
/**
* User 2FA status
*
* /api/users/123/2fa
*/
router
.route("/:user_id/2fa")
.options((_, res) => {
res.sendStatus(204);
})
.all(jwtdecode())
.all(userIdFromMe)
/**
* POST /api/users/123/2fa
*
* Start 2FA setup, returns QR code URL
*/
.post(async (req, res, next) => {
try {
const result = await internal2FA.startSetup(res.locals.access, req.params.user_id);
res.status(200).send(result);
} catch (err) {
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
})
/**
* GET /api/users/123/2fa
*
* Get 2FA status for a user
*/
.get(async (req, res, next) => {
try {
const status = await internal2FA.getStatus(res.locals.access, req.params.user_id);
res.status(200).send(status);
} catch (err) {
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
})
/**
* DELETE /api/users/123/2fa?code=XXXXXX
*
* Disable 2FA for a user
*/
.delete(async (req, res, next) => {
try {
const code = typeof req.query.code === "string" ? req.query.code : null;
if (!code) {
throw new errs.ValidationError("Missing required parameter: code");
}
await internal2FA.disable(res.locals.access, req.params.user_id, code);
res.status(200).send(true);
} catch (err) {
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});
/**
* User 2FA enable
*
* /api/users/123/2fa/enable
*/
router
.route("/:user_id/2fa/enable")
.options((_, res) => {
res.sendStatus(204);
})
.all(jwtdecode())
.all(userIdFromMe)
/**
* POST /api/users/123/2fa/enable
*
* Verify code and enable 2FA
*/
.post(async (req, res, next) => {
try {
const { code } = await apiValidator(
getValidationSchema("/users/{userID}/2fa/enable", "post"),
req.body,
);
const result = await internal2FA.enable(res.locals.access, req.params.user_id, code);
res.status(200).send(result);
} catch (err) {
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});
/**
* User 2FA backup codes
*
* /api/users/123/2fa/backup-codes
*/
router
.route("/:user_id/2fa/backup-codes")
.options((_, res) => {
res.sendStatus(204);
})
.all(jwtdecode())
.all(userIdFromMe)
/**
* POST /api/users/123/2fa/backup-codes
*
* Regenerate backup codes
*/
.post(async (req, res, next) => {
try {
const { code } = await apiValidator(
getValidationSchema("/users/{userID}/2fa/backup-codes", "post"),
req.body,
);
const result = await internal2FA.regenerateBackupCodes(res.locals.access, req.params.user_id, code);
res.status(200).send(result);
} catch (err) {
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
next(err);
}
});
export default router; export default router;

40
backend/routes/version.js Normal file
View File

@@ -0,0 +1,40 @@
import express from "express";
import internalRemoteVersion from "../internal/remote-version.js";
import { debug, express as logger } from "../logger.js";
const router = express.Router({
caseSensitive: true,
strict: true,
mergeParams: true,
});
/**
* /api/version/check
*/
router
.route("/check")
.options((_, res) => {
res.sendStatus(204);
})
/**
* GET /api/version/check
*
* Check for available updates
*/
.get(async (req, res, _next) => {
try {
const data = await internalRemoteVersion.get();
res.status(200).send(data);
} catch (error) {
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${error}`);
// Send 200 even though there's an error to avoid triggering update checks repeatedly
res.status(200).send({
current: null,
latest: null,
update_available: false,
});
}
});
export default router;

View File

@@ -71,6 +71,11 @@
"propagation_seconds": { "propagation_seconds": {
"type": "integer", "type": "integer",
"minimum": 0 "minimum": 0
},
"key_type": {
"type": "string",
"enum": ["rsa", "ecdsa"],
"default": "rsa"
} }
}, },
"example": { "example": {

View File

@@ -0,0 +1,23 @@
{
"type": "object",
"description": "Check Version object",
"additionalProperties": false,
"required": ["current", "latest", "update_available"],
"properties": {
"current": {
"type": ["string", "null"],
"description": "Current version string",
"example": "v2.10.1"
},
"latest": {
"type": ["string", "null"],
"description": "Latest version string",
"example": "v2.13.4"
},
"update_available": {
"type": "boolean",
"description": "Whether there's an update available",
"example": true
}
}
}

View File

@@ -0,0 +1,18 @@
{
"type": "object",
"description": "Token object",
"required": ["requires_2fa", "challenge_token"],
"additionalProperties": false,
"properties": {
"requires_2fa": {
"description": "Whether this token request requires two-factor authentication",
"example": true,
"type": "boolean"
},
"challenge_token": {
"description": "Challenge Token used in subsequent 2FA verification",
"example": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4",
"type": "string"
}
}
}

View File

@@ -0,0 +1,55 @@
{
"operationId": "loginWith2FA",
"summary": "Verify 2FA code and get full token",
"tags": ["tokens"],
"requestBody": {
"description": "2fa Challenge Payload",
"required": true,
"content": {
"application/json": {
"schema": {
"additionalProperties": false,
"properties": {
"challenge_token": {
"minLength": 1,
"type": "string",
"example": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4"
},
"code": {
"minLength": 6,
"maxLength": 6,
"type": "string",
"example": "012345"
}
},
"required": ["challenge_token", "code"],
"type": "object"
},
"example": {
"challenge_token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4",
"code": "012345"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"default": {
"value": {
"expires": "2025-02-04T20:40:46.340Z",
"token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4"
}
}
},
"schema": {
"$ref": "../../../components/token-object.json"
}
}
},
"description": "200 response"
}
}
}

View File

@@ -50,7 +50,14 @@
} }
}, },
"schema": { "schema": {
"oneOf": [
{
"$ref": "../../components/token-object.json" "$ref": "../../components/token-object.json"
},
{
"$ref": "../../components/token-challenge.json"
}
]
} }
} }
}, },

View File

@@ -0,0 +1,92 @@
{
"operationId": "regenUser2faCodes",
"summary": "Regenerate 2FA backup codes",
"tags": ["users"],
"parameters": [
{
"in": "path",
"name": "userID",
"schema": {
"type": "integer",
"minimum": 1
},
"required": true,
"description": "User ID",
"example": 2
}
],
"requestBody": {
"description": "Verififcation Payload",
"required": true,
"content": {
"application/json": {
"schema": {
"additionalProperties": false,
"properties": {
"code": {
"minLength": 6,
"maxLength": 6,
"type": "string",
"example": "123456"
}
},
"required": ["code"],
"type": "object"
},
"example": {
"code": "123456"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"default": {
"value": {
"backup_codes": [
"6CD7CB06",
"495302F3",
"D8037852",
"A6FFC956",
"BC1A1851",
"A05E644F",
"A406D2E8",
"0AE3C522"
]
}
}
},
"schema": {
"type": "object",
"required": ["backup_codes"],
"additionalProperties": false,
"properties": {
"backup_codes": {
"description": "Backup codes",
"example": [
"6CD7CB06",
"495302F3",
"D8037852",
"A6FFC956",
"BC1A1851",
"A05E644F",
"A406D2E8",
"0AE3C522"
],
"type": "array",
"items": {
"type": "string",
"example": "6CD7CB06"
}
}
}
}
}
},
"description": "200 response"
}
}
}

View File

@@ -0,0 +1,48 @@
{
"operationId": "disableUser2fa",
"summary": "Disable 2fa for user",
"tags": ["users"],
"parameters": [
{
"in": "path",
"name": "userID",
"schema": {
"type": "integer",
"minimum": 1
},
"required": true,
"description": "User ID",
"example": 2
},
{
"in": "query",
"name": "code",
"schema": {
"type": "string",
"minLength": 6,
"maxLength": 6,
"example": "012345"
},
"required": true,
"description": "2fa Code",
"example": "012345"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"default": {
"value": true
}
},
"schema": {
"type": "boolean"
}
}
},
"description": "200 response"
}
}
}

View File

@@ -0,0 +1,92 @@
{
"operationId": "enableUser2fa",
"summary": "Verify code and enable 2FA",
"tags": ["users"],
"parameters": [
{
"in": "path",
"name": "userID",
"schema": {
"type": "integer",
"minimum": 1
},
"required": true,
"description": "User ID",
"example": 2
}
],
"requestBody": {
"description": "Verififcation Payload",
"required": true,
"content": {
"application/json": {
"schema": {
"additionalProperties": false,
"properties": {
"code": {
"minLength": 6,
"maxLength": 6,
"type": "string",
"example": "123456"
}
},
"required": ["code"],
"type": "object"
},
"example": {
"code": "123456"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"default": {
"value": {
"backup_codes": [
"6CD7CB06",
"495302F3",
"D8037852",
"A6FFC956",
"BC1A1851",
"A05E644F",
"A406D2E8",
"0AE3C522"
]
}
}
},
"schema": {
"type": "object",
"required": ["backup_codes"],
"additionalProperties": false,
"properties": {
"backup_codes": {
"description": "Backup codes",
"example": [
"6CD7CB06",
"495302F3",
"D8037852",
"A6FFC956",
"BC1A1851",
"A05E644F",
"A406D2E8",
"0AE3C522"
],
"type": "array",
"items": {
"type": "string",
"example": "6CD7CB06"
}
}
}
}
}
},
"description": "200 response"
}
}
}

View File

@@ -0,0 +1,57 @@
{
"operationId": "getUser2faStatus",
"summary": "Get user 2fa Status",
"tags": ["users"],
"security": [
{
"bearerAuth": []
}
],
"parameters": [
{
"in": "path",
"name": "userID",
"schema": {
"type": "integer",
"minimum": 1
},
"required": true,
"description": "User ID",
"example": 2
}
],
"responses": {
"200": {
"description": "200 response",
"content": {
"application/json": {
"examples": {
"default": {
"value": {
"enabled": false,
"backup_codes_remaining": 0
}
}
},
"schema": {
"type": "object",
"additionalProperties": false,
"required": ["enabled", "backup_codes_remaining"],
"properties": {
"enabled": {
"type": "boolean",
"description": "Is 2FA enabled for this user",
"example": true
},
"backup_codes_remaining": {
"type": "integer",
"description": "Number of remaining backup codes for this user",
"example": 5
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,52 @@
{
"operationId": "setupUser2fa",
"summary": "Start 2FA setup, returns QR code URL",
"tags": ["users"],
"parameters": [
{
"in": "path",
"name": "userID",
"schema": {
"type": "integer",
"minimum": 1
},
"required": true,
"description": "User ID",
"example": 2
}
],
"responses": {
"200": {
"content": {
"application/json": {
"examples": {
"default": {
"value": {
"secret": "JZYCEBIEEJYUGPQM",
"otpauth_url": "otpauth://totp/Nginx%20Proxy%20Manager:jc%40jc21.com?secret=JZYCEBIEEJYUGPQM&period=30&digits=6&algorithm=SHA1&issuer=Nginx%20Proxy%20Manager"
}
}
},
"schema": {
"type": "object",
"required": ["secret", "otpauth_url"],
"additionalProperties": false,
"properties": {
"secret": {
"description": "TOTP Secret",
"example": "JZYCEBIEEJYUGPQM",
"type": "string"
},
"otpauth_url": {
"description": "OTP Auth URL for QR Code generation",
"example": "otpauth://totp/Nginx%20Proxy%20Manager:jc%40jc21.com?secret=JZYCEBIEEJYUGPQM&period=30&digits=6&algorithm=SHA1&issuer=Nginx%20Proxy%20Manager",
"type": "string"
}
}
}
}
},
"description": "200 response"
}
}
}

View File

@@ -0,0 +1,26 @@
{
"operationId": "checkVersion",
"summary": "Returns any new version data from github",
"tags": ["public"],
"responses": {
"200": {
"description": "200 response",
"content": {
"application/json": {
"examples": {
"default": {
"value": {
"current": "v2.12.0",
"latest": "v2.13.4",
"update_available": true
}
}
},
"schema": {
"$ref": "../../../components/check-version-object.json"
}
}
}
}
}
}

View File

@@ -293,6 +293,16 @@
"$ref": "./paths/tokens/post.json" "$ref": "./paths/tokens/post.json"
} }
}, },
"/tokens/2fa": {
"post": {
"$ref": "./paths/tokens/2fa/post.json"
}
},
"/version/check": {
"get": {
"$ref": "./paths/version/check/get.json"
}
},
"/users": { "/users": {
"get": { "get": {
"$ref": "./paths/users/get.json" "$ref": "./paths/users/get.json"
@@ -312,6 +322,27 @@
"$ref": "./paths/users/userID/delete.json" "$ref": "./paths/users/userID/delete.json"
} }
}, },
"/users/{userID}/2fa": {
"post": {
"$ref": "./paths/users/userID/2fa/post.json"
},
"get": {
"$ref": "./paths/users/userID/2fa/get.json"
},
"delete": {
"$ref": "./paths/users/userID/2fa/delete.json"
}
},
"/users/{userID}/2fa/enable": {
"post": {
"$ref": "./paths/users/userID/2fa/enable/post.json"
}
},
"/users/{userID}/2fa/backup-codes": {
"post": {
"$ref": "./paths/users/userID/2fa/backup-codes/post.json"
}
},
"/users/{userID}/auth": { "/users/{userID}/auth": {
"put": { "put": {
"$ref": "./paths/users/userID/auth/put.json" "$ref": "./paths/users/userID/auth/put.json"

View File

@@ -12,6 +12,9 @@ server {
proxy_pass {{ forwarding_host }}:{{ forwarding_port }}; proxy_pass {{ forwarding_host }}:{{ forwarding_port }};
access_log /data/logs/stream-{{ id }}_access.log stream;
error_log /data/logs/stream-{{ id }}_error.log warn;
# Custom # Custom
include /data/nginx/custom/server_stream[.]conf; include /data/nginx/custom/server_stream[.]conf;
include /data/nginx/custom/server_stream_tcp[.]conf; include /data/nginx/custom/server_stream_tcp[.]conf;
@@ -25,6 +28,9 @@ server {
proxy_pass {{ forwarding_host }}:{{ forwarding_port }}; proxy_pass {{ forwarding_host }}:{{ forwarding_port }};
access_log /data/logs/stream-{{ id }}_access.log stream;
error_log /data/logs/stream-{{ id }}_error.log warn;
# Custom # Custom
include /data/nginx/custom/server_stream[.]conf; include /data/nginx/custom/server_stream[.]conf;
include /data/nginx/custom/server_stream_udp[.]conf; include /data/nginx/custom/server_stream_udp[.]conf;

View File

@@ -138,6 +138,44 @@
mkdirp "^1.0.4" mkdirp "^1.0.4"
rimraf "^3.0.2" rimraf "^3.0.2"
"@otplib/core@^12.0.1":
version "12.0.1"
resolved "https://registry.yarnpkg.com/@otplib/core/-/core-12.0.1.tgz#73720a8cedce211fe5b3f683cd5a9c098eaf0f8d"
integrity sha512-4sGntwbA/AC+SbPhbsziRiD+jNDdIzsZ3JUyfZwjtKyc/wufl1pnSIaG4Uqx8ymPagujub0o92kgBnB89cuAMA==
"@otplib/plugin-crypto@^12.0.1":
version "12.0.1"
resolved "https://registry.yarnpkg.com/@otplib/plugin-crypto/-/plugin-crypto-12.0.1.tgz#2b42c624227f4f9303c1c041fca399eddcbae25e"
integrity sha512-qPuhN3QrT7ZZLcLCyKOSNhuijUi9G5guMRVrxq63r9YNOxxQjPm59gVxLM+7xGnHnM6cimY57tuKsjK7y9LM1g==
dependencies:
"@otplib/core" "^12.0.1"
"@otplib/plugin-thirty-two@^12.0.1":
version "12.0.1"
resolved "https://registry.yarnpkg.com/@otplib/plugin-thirty-two/-/plugin-thirty-two-12.0.1.tgz#5cc9b56e6e89f2a1fe4a2b38900ca4e11c87aa9e"
integrity sha512-MtT+uqRso909UkbrrYpJ6XFjj9D+x2Py7KjTO9JDPhL0bJUYVu5kFP4TFZW4NFAywrAtFRxOVY261u0qwb93gA==
dependencies:
"@otplib/core" "^12.0.1"
thirty-two "^1.0.2"
"@otplib/preset-default@^12.0.1":
version "12.0.1"
resolved "https://registry.yarnpkg.com/@otplib/preset-default/-/preset-default-12.0.1.tgz#cb596553c08251e71b187ada4a2246ad2a3165ba"
integrity sha512-xf1v9oOJRyXfluBhMdpOkr+bsE+Irt+0D5uHtvg6x1eosfmHCsCC6ej/m7FXiWqdo0+ZUI6xSKDhJwc8yfiOPQ==
dependencies:
"@otplib/core" "^12.0.1"
"@otplib/plugin-crypto" "^12.0.1"
"@otplib/plugin-thirty-two" "^12.0.1"
"@otplib/preset-v11@^12.0.1":
version "12.0.1"
resolved "https://registry.yarnpkg.com/@otplib/preset-v11/-/preset-v11-12.0.1.tgz#4c7266712e7230500b421ba89252963c838fc96d"
integrity sha512-9hSetMI7ECqbFiKICrNa4w70deTUfArtwXykPUvSHWOdzOlfa9ajglu7mNCntlvxycTiOAXkQGwjQCzzDEMRMg==
dependencies:
"@otplib/core" "^12.0.1"
"@otplib/plugin-crypto" "^12.0.1"
"@otplib/plugin-thirty-two" "^12.0.1"
"@tootallnate/once@1": "@tootallnate/once@1":
version "1.1.2" version "1.1.2"
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
@@ -389,23 +427,23 @@ blueimp-md5@^2.16.0:
resolved "https://registry.yarnpkg.com/blueimp-md5/-/blueimp-md5-2.19.0.tgz#b53feea5498dcb53dc6ec4b823adb84b729c4af0" resolved "https://registry.yarnpkg.com/blueimp-md5/-/blueimp-md5-2.19.0.tgz#b53feea5498dcb53dc6ec4b823adb84b729c4af0"
integrity sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w== integrity sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==
body-parser@1.20.3, body-parser@^1.20.3: body-parser@^1.20.3, body-parser@~1.20.3:
version "1.20.3" version "1.20.4"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.4.tgz#f8e20f4d06ca8a50a71ed329c15dccad1cdc547f"
integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== integrity sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==
dependencies: dependencies:
bytes "3.1.2" bytes "~3.1.2"
content-type "~1.0.5" content-type "~1.0.5"
debug "2.6.9" debug "2.6.9"
depd "2.0.0" depd "2.0.0"
destroy "1.2.0" destroy "~1.2.0"
http-errors "2.0.0" http-errors "~2.0.1"
iconv-lite "0.4.24" iconv-lite "~0.4.24"
on-finished "2.4.1" on-finished "~2.4.1"
qs "6.13.0" qs "~6.14.0"
raw-body "2.5.2" raw-body "~2.5.3"
type-is "~1.6.18" type-is "~1.6.18"
unpipe "1.0.0" unpipe "~1.0.0"
brace-expansion@^1.1.7: brace-expansion@^1.1.7:
version "1.1.12" version "1.1.12"
@@ -454,7 +492,7 @@ busboy@^1.6.0:
dependencies: dependencies:
streamsearch "^1.1.0" streamsearch "^1.1.0"
bytes@3.1.2: bytes@3.1.2, bytes@~3.1.2:
version "3.1.2" version "3.1.2"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
@@ -649,7 +687,7 @@ console-control-strings@^1.0.0, console-control-strings@^1.1.0:
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==
content-disposition@0.5.4: content-disposition@~0.5.4:
version "0.5.4" version "0.5.4"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==
@@ -661,15 +699,15 @@ content-type@~1.0.4, content-type@~1.0.5:
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
cookie-signature@1.0.6: cookie-signature@~1.0.6:
version "1.0.6" version "1.0.7"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.7.tgz#ab5dd7ab757c54e60f37ef6550f481c426d10454"
integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== integrity sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==
cookie@0.7.1: cookie@~0.7.1:
version "0.7.1" version "0.7.2"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7"
integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w== integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==
core-util-is@~1.0.0: core-util-is@~1.0.0:
version "1.0.3" version "1.0.3"
@@ -706,10 +744,10 @@ debug@2.6.9:
dependencies: dependencies:
ms "2.0.0" ms "2.0.0"
debug@4, debug@^4.3.3: debug@4, debug@^4.3.3, debug@^4.3.4:
version "4.4.1" version "4.4.3"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
dependencies: dependencies:
ms "^2.1.3" ms "^2.1.3"
@@ -727,13 +765,6 @@ debug@^3.2.7:
dependencies: dependencies:
ms "^2.1.1" ms "^2.1.1"
debug@^4.3.4:
version "4.4.3"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
dependencies:
ms "^2.1.3"
decamelize@^1.2.0: decamelize@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
@@ -770,12 +801,12 @@ denque@^2.1.0:
resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1"
integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==
depd@2.0.0: depd@2.0.0, depd@~2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
destroy@1.2.0: destroy@1.2.0, destroy@~1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
@@ -816,11 +847,6 @@ emoji-regex@^8.0.0:
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
encodeurl@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
encodeurl@~2.0.0: encodeurl@~2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
@@ -937,39 +963,39 @@ express-fileupload@^1.5.2:
dependencies: dependencies:
busboy "^1.6.0" busboy "^1.6.0"
express@^4.20.0: express@^4.22.0:
version "4.21.2" version "4.22.0"
resolved "https://registry.yarnpkg.com/express/-/express-4.21.2.tgz#cf250e48362174ead6cea4a566abef0162c1ec32" resolved "https://registry.yarnpkg.com/express/-/express-4.22.0.tgz#a9d7abdce6d774ed1b4479019387763d1798bd03"
integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA== integrity sha512-c2iPh3xp5vvCLgaHK03+mWLFPhox7j1LwyxcZwFVApEv5i0X+IjPpbT50SJJwwLpdBVfp45AkK/v+AFgv/XlfQ==
dependencies: dependencies:
accepts "~1.3.8" accepts "~1.3.8"
array-flatten "1.1.1" array-flatten "1.1.1"
body-parser "1.20.3" body-parser "~1.20.3"
content-disposition "0.5.4" content-disposition "~0.5.4"
content-type "~1.0.4" content-type "~1.0.4"
cookie "0.7.1" cookie "~0.7.1"
cookie-signature "1.0.6" cookie-signature "~1.0.6"
debug "2.6.9" debug "2.6.9"
depd "2.0.0" depd "2.0.0"
encodeurl "~2.0.0" encodeurl "~2.0.0"
escape-html "~1.0.3" escape-html "~1.0.3"
etag "~1.8.1" etag "~1.8.1"
finalhandler "1.3.1" finalhandler "~1.3.1"
fresh "0.5.2" fresh "~0.5.2"
http-errors "2.0.0" http-errors "~2.0.0"
merge-descriptors "1.0.3" merge-descriptors "1.0.3"
methods "~1.1.2" methods "~1.1.2"
on-finished "2.4.1" on-finished "~2.4.1"
parseurl "~1.3.3" parseurl "~1.3.3"
path-to-regexp "0.1.12" path-to-regexp "~0.1.12"
proxy-addr "~2.0.7" proxy-addr "~2.0.7"
qs "6.13.0" qs "~6.14.0"
range-parser "~1.2.1" range-parser "~1.2.1"
safe-buffer "5.2.1" safe-buffer "5.2.1"
send "0.19.0" send "~0.19.0"
serve-static "1.16.2" serve-static "~1.16.2"
setprototypeof "1.2.0" setprototypeof "1.2.0"
statuses "2.0.1" statuses "~2.0.1"
type-is "~1.6.18" type-is "~1.6.18"
utils-merge "1.0.1" utils-merge "1.0.1"
vary "~1.1.2" vary "~1.1.2"
@@ -1003,17 +1029,17 @@ fill-range@^7.1.1:
dependencies: dependencies:
to-regex-range "^5.0.1" to-regex-range "^5.0.1"
finalhandler@1.3.1: finalhandler@~1.3.1:
version "1.3.1" version "1.3.2"
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.2.tgz#1ebc2228fc7673aac4a472c310cc05b77d852b88"
integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ== integrity sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==
dependencies: dependencies:
debug "2.6.9" debug "2.6.9"
encodeurl "~2.0.0" encodeurl "~2.0.0"
escape-html "~1.0.3" escape-html "~1.0.3"
on-finished "2.4.1" on-finished "~2.4.1"
parseurl "~1.3.3" parseurl "~1.3.3"
statuses "2.0.1" statuses "~2.0.2"
unpipe "~1.0.0" unpipe "~1.0.0"
find-up@^2.0.0: find-up@^2.0.0:
@@ -1036,7 +1062,7 @@ forwarded@0.2.0:
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
fresh@0.5.2: fresh@~0.5.2:
version "0.5.2" version "0.5.2"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
@@ -1228,16 +1254,16 @@ http-cache-semantics@^4.1.0:
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz#205f4db64f8562b76a4ff9235aa5279839a09dd5" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz#205f4db64f8562b76a4ff9235aa5279839a09dd5"
integrity sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ== integrity sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==
http-errors@2.0.0: http-errors@~2.0.0, http-errors@~2.0.1:
version "2.0.0" version "2.0.1"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.1.tgz#36d2f65bc909c8790018dd36fb4d93da6caae06b"
integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==
dependencies: dependencies:
depd "2.0.0" depd "~2.0.0"
inherits "2.0.4" inherits "~2.0.4"
setprototypeof "1.2.0" setprototypeof "~1.2.0"
statuses "2.0.1" statuses "~2.0.2"
toidentifier "1.0.1" toidentifier "~1.0.1"
http-proxy-agent@^4.0.1: http-proxy-agent@^4.0.1:
version "4.0.1" version "4.0.1"
@@ -1279,13 +1305,6 @@ humanize-ms@^1.2.1:
dependencies: dependencies:
ms "^2.0.0" ms "^2.0.0"
iconv-lite@0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
dependencies:
safer-buffer ">= 2.1.2 < 3"
iconv-lite@^0.6.2: iconv-lite@^0.6.2:
version "0.6.3" version "0.6.3"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
@@ -1300,6 +1319,13 @@ iconv-lite@^0.7.0:
dependencies: dependencies:
safer-buffer ">= 2.1.2 < 3.0.0" safer-buffer ">= 2.1.2 < 3.0.0"
iconv-lite@~0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
dependencies:
safer-buffer ">= 2.1.2 < 3"
ieee754@^1.1.13: ieee754@^1.1.13:
version "1.2.1" version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
@@ -1333,7 +1359,7 @@ inflight@^1.0.4:
once "^1.3.0" once "^1.3.0"
wrappy "1" wrappy "1"
inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: inherits@2, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3, inherits@~2.0.4:
version "2.0.4" version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -1430,9 +1456,9 @@ isexe@^2.0.0:
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
js-yaml@^4.1.0: js-yaml@^4.1.0:
version "4.1.0" version "4.1.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b"
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==
dependencies: dependencies:
argparse "^2.0.1" argparse "^2.0.1"
@@ -1462,7 +1488,7 @@ jsonwebtoken@^9.0.2:
ms "^2.1.1" ms "^2.1.1"
semver "^7.5.4" semver "^7.5.4"
jwa@^1.4.1: jwa@^1.4.2:
version "1.4.2" version "1.4.2"
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.2.tgz#16011ac6db48de7b102777e57897901520eec7b9" resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.2.tgz#16011ac6db48de7b102777e57897901520eec7b9"
integrity sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw== integrity sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==
@@ -1472,11 +1498,11 @@ jwa@^1.4.1:
safe-buffer "^5.0.1" safe-buffer "^5.0.1"
jws@^3.2.2: jws@^3.2.2:
version "3.2.2" version "3.2.3"
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.3.tgz#5ac0690b460900a27265de24520526853c0b8ca1"
integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== integrity sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==
dependencies: dependencies:
jwa "^1.4.1" jwa "^1.4.2"
safe-buffer "^5.0.1" safe-buffer "^5.0.1"
knex@2.4.2: knex@2.4.2:
@@ -1959,7 +1985,7 @@ objection@3.0.1:
ajv "^8.6.2" ajv "^8.6.2"
db-errors "^0.2.3" db-errors "^0.2.3"
on-finished@2.4.1: on-finished@~2.4.1:
version "2.4.1" version "2.4.1"
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
@@ -1978,6 +2004,15 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0:
dependencies: dependencies:
wrappy "1" wrappy "1"
otplib@^12.0.1:
version "12.0.1"
resolved "https://registry.yarnpkg.com/otplib/-/otplib-12.0.1.tgz#c1d3060ab7aadf041ed2960302f27095777d1f73"
integrity sha512-xDGvUOQjop7RDgxTQ+o4pOol0/3xSZzawTiPKRrHnQWAy0WjhNs/5HdIDJCrqC4MBynmjXgULc6YfioaxZeFgg==
dependencies:
"@otplib/core" "^12.0.1"
"@otplib/preset-default" "^12.0.1"
"@otplib/preset-v11" "^12.0.1"
p-limit@^1.1.0: p-limit@^1.1.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8"
@@ -2078,7 +2113,7 @@ path-parse@^1.0.7:
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
path-to-regexp@0.1.12: path-to-regexp@~0.1.12:
version "0.1.12" version "0.1.12"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7"
integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==
@@ -2273,12 +2308,12 @@ pump@^3.0.0:
end-of-stream "^1.1.0" end-of-stream "^1.1.0"
once "^1.3.1" once "^1.3.1"
qs@6.13.0: qs@~6.14.0:
version "6.13.0" version "6.14.1"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.1.tgz#a41d85b9d3902f31d27861790506294881871159"
integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== integrity sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==
dependencies: dependencies:
side-channel "^1.0.6" side-channel "^1.1.0"
querystring@0.2.0: querystring@0.2.0:
version "0.2.0" version "0.2.0"
@@ -2290,15 +2325,15 @@ range-parser@~1.2.1:
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
raw-body@2.5.2: raw-body@~2.5.3:
version "2.5.2" version "2.5.3"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.3.tgz#11c6650ee770a7de1b494f197927de0c923822e2"
integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== integrity sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==
dependencies: dependencies:
bytes "3.1.2" bytes "~3.1.2"
http-errors "2.0.0" http-errors "~2.0.1"
iconv-lite "0.4.24" iconv-lite "~0.4.24"
unpipe "1.0.0" unpipe "~1.0.0"
rc@^1.2.7: rc@^1.2.7:
version "1.2.8" version "1.2.8"
@@ -2429,46 +2464,46 @@ semver@~7.0.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
send@0.19.0: send@~0.19.0, send@~0.19.1:
version "0.19.0" version "0.19.2"
resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" resolved "https://registry.yarnpkg.com/send/-/send-0.19.2.tgz#59bc0da1b4ea7ad42736fd642b1c4294e114ff29"
integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== integrity sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==
dependencies: dependencies:
debug "2.6.9" debug "2.6.9"
depd "2.0.0" depd "2.0.0"
destroy "1.2.0" destroy "1.2.0"
encodeurl "~1.0.2" encodeurl "~2.0.0"
escape-html "~1.0.3" escape-html "~1.0.3"
etag "~1.8.1" etag "~1.8.1"
fresh "0.5.2" fresh "~0.5.2"
http-errors "2.0.0" http-errors "~2.0.1"
mime "1.6.0" mime "1.6.0"
ms "2.1.3" ms "2.1.3"
on-finished "2.4.1" on-finished "~2.4.1"
range-parser "~1.2.1" range-parser "~1.2.1"
statuses "2.0.1" statuses "~2.0.2"
seq-queue@^0.0.5: seq-queue@^0.0.5:
version "0.0.5" version "0.0.5"
resolved "https://registry.yarnpkg.com/seq-queue/-/seq-queue-0.0.5.tgz#d56812e1c017a6e4e7c3e3a37a1da6d78dd3c93e" resolved "https://registry.yarnpkg.com/seq-queue/-/seq-queue-0.0.5.tgz#d56812e1c017a6e4e7c3e3a37a1da6d78dd3c93e"
integrity sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q== integrity sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==
serve-static@1.16.2: serve-static@~1.16.2:
version "1.16.2" version "1.16.3"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.3.tgz#a97b74d955778583f3862a4f0b841eb4d5d78cf9"
integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== integrity sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==
dependencies: dependencies:
encodeurl "~2.0.0" encodeurl "~2.0.0"
escape-html "~1.0.3" escape-html "~1.0.3"
parseurl "~1.3.3" parseurl "~1.3.3"
send "0.19.0" send "~0.19.1"
set-blocking@^2.0.0: set-blocking@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==
setprototypeof@1.2.0: setprototypeof@1.2.0, setprototypeof@~1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
@@ -2502,7 +2537,7 @@ side-channel-weakmap@^1.0.2:
object-inspect "^1.13.3" object-inspect "^1.13.3"
side-channel-map "^1.0.1" side-channel-map "^1.0.1"
side-channel@^1.0.6: side-channel@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9"
integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==
@@ -2613,10 +2648,10 @@ ssri@^8.0.0, ssri@^8.0.1:
dependencies: dependencies:
minipass "^3.1.1" minipass "^3.1.1"
statuses@2.0.1: statuses@~2.0.1, statuses@~2.0.2:
version "2.0.1" version "2.0.2"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382"
integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==
streamsearch@^1.1.0: streamsearch@^1.1.0:
version "1.1.0" version "1.1.0"
@@ -2736,6 +2771,11 @@ temp-write@^4.0.0:
temp-dir "^1.0.0" temp-dir "^1.0.0"
uuid "^3.3.2" uuid "^3.3.2"
thirty-two@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/thirty-two/-/thirty-two-1.0.2.tgz#4ca2fffc02a51290d2744b9e3f557693ca6b627a"
integrity sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA==
tildify@2.0.0: tildify@2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/tildify/-/tildify-2.0.0.tgz#f205f3674d677ce698b7067a99e949ce03b4754a" resolved "https://registry.yarnpkg.com/tildify/-/tildify-2.0.0.tgz#f205f3674d677ce698b7067a99e949ce03b4754a"
@@ -2748,7 +2788,7 @@ to-regex-range@^5.0.1:
dependencies: dependencies:
is-number "^7.0.0" is-number "^7.0.0"
toidentifier@1.0.1: toidentifier@~1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
@@ -2802,7 +2842,7 @@ unique-slug@^2.0.0:
dependencies: dependencies:
imurmurhash "^0.1.4" imurmurhash "^0.1.4"
unpipe@1.0.0, unpipe@~1.0.0: unpipe@~1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==

View File

@@ -24,9 +24,13 @@ services:
interval: 10s interval: 10s
timeout: 3s timeout: 3s
expose: expose:
- "80-81/tcp" - "80/tcp"
- "81/tcp"
- "443/tcp" - "443/tcp"
- "1500-1503/tcp" - "1500/tcp"
- "1501/tcp"
- "1502/tcp"
- "1503/tcp"
networks: networks:
fulltest: fulltest:
aliases: aliases:

View File

@@ -8,8 +8,8 @@ server {
set $port "80"; set $port "80";
server_name localhost-nginx-proxy-manager; server_name localhost-nginx-proxy-manager;
access_log /data/logs/fallback_access.log standard; access_log /data/logs/fallback_http_access.log standard;
error_log /data/logs/fallback_error.log warn; error_log /data/logs/fallback_http_error.log warn;
include conf.d/include/assets.conf; include conf.d/include/assets.conf;
include conf.d/include/block-exploits.conf; include conf.d/include/block-exploits.conf;
include conf.d/include/letsencrypt-acme-challenge.conf; include conf.d/include/letsencrypt-acme-challenge.conf;
@@ -30,7 +30,7 @@ server {
set $port "443"; set $port "443";
server_name localhost; server_name localhost;
access_log /data/logs/fallback_access.log standard; access_log /data/logs/fallback_http_access.log standard;
error_log /dev/null crit; error_log /dev/null crit;
include conf.d/include/ssl-ciphers.conf; include conf.d/include/ssl-ciphers.conf;
ssl_reject_handshake on; ssl_reject_handshake on;

View File

@@ -1,4 +1,4 @@
log_format proxy '[$time_local] $upstream_cache_status $upstream_status $status - $request_method $scheme $host "$request_uri" [Client $remote_addr] [Length $body_bytes_sent] [Gzip $gzip_ratio] [Sent-to $server] "$http_user_agent" "$http_referer"'; log_format proxy '[$time_local] $upstream_cache_status $upstream_status $status - $request_method $scheme $host "$request_uri" [Client $remote_addr] [Length $body_bytes_sent] [Gzip $gzip_ratio] [Sent-to $server] "$http_user_agent" "$http_referer"';
log_format standard '[$time_local] $status - $request_method $scheme $host "$request_uri" [Client $remote_addr] [Length $body_bytes_sent] [Gzip $gzip_ratio] "$http_user_agent" "$http_referer"'; log_format standard '[$time_local] $status - $request_method $scheme $host "$request_uri" [Client $remote_addr] [Length $body_bytes_sent] [Gzip $gzip_ratio] "$http_user_agent" "$http_referer"';
access_log /data/logs/fallback_access.log proxy; access_log /data/logs/fallback_http_access.log proxy;

View File

@@ -0,0 +1,3 @@
log_format stream '[$time_local] [Client $remote_addr:$remote_port] $protocol $status $bytes_sent $bytes_received $session_time [Sent-to $upstream_addr] [Sent $upstream_bytes_sent] [Received $upstream_bytes_received] [Time $upstream_connect_time] $ssl_protocol $ssl_cipher';
access_log /data/logs/fallback_stream_access.log stream;

View File

@@ -47,7 +47,7 @@ http {
proxy_cache_path /var/lib/nginx/cache/private levels=1:2 keys_zone=private-cache:5m max_size=1024m; proxy_cache_path /var/lib/nginx/cache/private levels=1:2 keys_zone=private-cache:5m max_size=1024m;
# Log format and fallback log file # Log format and fallback log file
include /etc/nginx/conf.d/include/log.conf; include /etc/nginx/conf.d/include/log-proxy.conf;
# Dynamically generated resolvers file # Dynamically generated resolvers file
include /etc/nginx/conf.d/include/resolvers.conf; include /etc/nginx/conf.d/include/resolvers.conf;
@@ -85,6 +85,9 @@ http {
} }
stream { stream {
# Log format and fallback log file
include /etc/nginx/conf.d/include/log-stream.conf;
# Files generated by NPM # Files generated by NPM
include /data/nginx/stream/*.conf; include /data/nginx/stream/*.conf;

File diff suppressed because it is too large Load Diff

2
frontend/.gitignore vendored
View File

@@ -1,3 +1,5 @@
src/locale/lang
# Logs # Logs
logs logs
*.log *.log

View File

@@ -8,7 +8,19 @@
const allLocales = [ const allLocales = [
["en", "en-US"], ["en", "en-US"],
["de", "de-DE"],
["es", "es-ES"], ["es", "es-ES"],
["it", "it-IT"],
["ja", "ja-JP"],
["nl", "nl-NL"],
["pl", "pl-PL"],
["ru", "ru-RU"],
["sk", "sk-SK"],
["vi", "vi-VN"],
["zh", "zh-CN"],
["ko", "ko-KR"],
["bg", "bg-BG"],
["id", "id-ID"],
]; ];
const ignoreUnused = [ const ignoreUnused = [

View File

@@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Nginx Proxy Manager</title> <title>Nginx Proxy Manager</title>
<meta name="description" content="In The Office Planner" /> <meta name="description" content="In The Office Planner" />
<link rel="preload" href="/images/logo-no-text.svg" as="image" type="image/svg+xml" fetchPriority="high">
<link <link
rel="apple-touch-icon" rel="apple-touch-icon"
sizes="180x180" sizes="180x180"

View File

@@ -29,9 +29,9 @@
"generate-password-browser": "^1.1.0", "generate-password-browser": "^1.1.0",
"humps": "^2.0.1", "humps": "^2.0.1",
"query-string": "^9.3.1", "query-string": "^9.3.1",
"react": "^19.2.0", "react": "^19.2.3",
"react-bootstrap": "^2.10.10", "react-bootstrap": "^2.10.10",
"react-dom": "^19.2.0", "react-dom": "^19.2.3",
"react-intl": "^7.1.14", "react-intl": "^7.1.14",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"react-router-dom": "^7.9.5", "react-router-dom": "^7.9.5",
@@ -48,10 +48,10 @@
"@testing-library/react": "^16.3.0", "@testing-library/react": "^16.3.0",
"@types/country-flag-icons": "^1.2.2", "@types/country-flag-icons": "^1.2.2",
"@types/humps": "^2.0.6", "@types/humps": "^2.0.6",
"@types/react": "^19.2.2", "@types/react": "^19.2.7",
"@types/react-dom": "^19.2.2", "@types/react-dom": "^19.2.3",
"@types/react-table": "^7.7.20", "@types/react-table": "^7.7.20",
"@vitejs/plugin-react": "^5.1.0", "@vitejs/plugin-react": "^5.1.2",
"happy-dom": "^20.0.10", "happy-dom": "^20.0.10",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"postcss-simple-vars": "^7.0.1", "postcss-simple-vars": "^7.0.1",

View File

@@ -13,6 +13,15 @@
--tblr-backdrop-opacity: 0.8 !important; --tblr-backdrop-opacity: 0.8 !important;
} }
[data-bs-theme="dark"] .modal-content {
--tblr-modal-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;
}
[data-bs-theme="dark"] .modal-backdrop {
--tblr-backdrop-bg: #000 !important;
--tblr-backdrop-opacity: 0.65 !important;
}
.domain-name { .domain-name {
font-family: monospace; font-family: monospace;
} }
@@ -95,3 +104,15 @@ label.row {
border-radius: var(--tblr-border-radius) 0 0 var(--tblr-border-radius); border-radius: var(--tblr-border-radius) 0 0 var(--tblr-border-radius);
} }
} }
/* Fix for dropdown menus being clipped by table-responsive containers. */
.table-responsive .dropdown {
position: static;
}
/* Fix for Tabler scrollbar compensation */
@media (min-width: 992px) {
:host, :root {
margin-left: 0;
}
}

View File

@@ -156,7 +156,6 @@ export async function del({ url, params }: DeleteArgs, abortController?: AbortCo
const method = "DELETE"; const method = "DELETE";
const headers = { const headers = {
...buildAuthHeader(), ...buildAuthHeader(),
[contentTypeHeader]: "application/json",
}; };
const signal = abortController?.signal; const signal = abortController?.signal;
const response = await fetch(apiUrl, { method, headers, signal }); const response = await fetch(apiUrl, { method, headers, signal });

View File

@@ -0,0 +1,8 @@
import * as api from "./base";
import type { VersionCheckResponse } from "./responseTypes";
export async function checkVersion(): Promise<VersionCheckResponse> {
return await api.get({
url: "/version/check",
});
}

View File

@@ -1,9 +1,22 @@
import * as api from "./base"; import * as api from "./base";
import type { TokenResponse } from "./responseTypes"; import type { TokenResponse, TwoFactorChallengeResponse } from "./responseTypes";
export async function getToken(identity: string, secret: string): Promise<TokenResponse> { export type LoginResponse = TokenResponse | TwoFactorChallengeResponse;
export function isTwoFactorChallenge(response: LoginResponse): response is TwoFactorChallengeResponse {
return "requires2fa" in response && response.requires2fa === true;
}
export async function getToken(identity: string, secret: string): Promise<LoginResponse> {
return await api.post({ return await api.post({
url: "/tokens", url: "/tokens",
data: { identity, secret }, data: { identity, secret },
}); });
} }
export async function verify2FA(challengeToken: string, code: string): Promise<TokenResponse> {
return await api.post({
url: "/tokens/2fa",
data: { challengeToken, code },
});
}

View File

@@ -1,3 +1,4 @@
export * from "./checkVersion";
export * from "./createAccessList"; export * from "./createAccessList";
export * from "./createCertificate"; export * from "./createCertificate";
export * from "./createDeadHost"; export * from "./createDeadHost";
@@ -59,3 +60,4 @@ export * from "./updateStream";
export * from "./updateUser"; export * from "./updateUser";
export * from "./uploadCertificate"; export * from "./uploadCertificate";
export * from "./validateCertificate"; export * from "./validateCertificate";
export * from "./twoFactor";

View File

@@ -19,3 +19,28 @@ export interface ValidatedCertificateResponse {
export interface LoginAsTokenResponse extends TokenResponse { export interface LoginAsTokenResponse extends TokenResponse {
user: User; user: User;
} }
export interface VersionCheckResponse {
current: string | null;
latest: string | null;
updateAvailable: boolean;
}
export interface TwoFactorChallengeResponse {
requires2fa: boolean;
challengeToken: string;
}
export interface TwoFactorStatusResponse {
enabled: boolean;
backupCodesRemaining: number;
}
export interface TwoFactorSetupResponse {
secret: string;
otpauthUrl: string;
}
export interface TwoFactorEnableResponse {
backupCodes: string[];
}

View File

@@ -0,0 +1,37 @@
import * as api from "./base";
import type { TwoFactorEnableResponse, TwoFactorSetupResponse, TwoFactorStatusResponse } from "./responseTypes";
export async function get2FAStatus(userId: number | "me"): Promise<TwoFactorStatusResponse> {
return await api.get({
url: `/users/${userId}/2fa`,
});
}
export async function start2FASetup(userId: number | "me"): Promise<TwoFactorSetupResponse> {
return await api.post({
url: `/users/${userId}/2fa`,
});
}
export async function enable2FA(userId: number | "me", code: string): Promise<TwoFactorEnableResponse> {
return await api.post({
url: `/users/${userId}/2fa/enable`,
data: { code },
});
}
export async function disable2FA(userId: number | "me", code: string): Promise<boolean> {
return await api.del({
url: `/users/${userId}/2fa`,
params: {
code,
},
});
}
export async function regenerateBackupCodes(userId: number | "me", code: string): Promise<TwoFactorEnableResponse> {
return await api.post({
url: `/users/${userId}/2fa/backup-codes`,
data: { code },
});
}

View File

@@ -3,7 +3,7 @@ import cn from "classnames";
import { useFormikContext } from "formik"; import { useFormikContext } from "formik";
import { useState } from "react"; import { useState } from "react";
import type { AccessListClient } from "src/api/backend"; import type { AccessListClient } from "src/api/backend";
import { T } from "src/locale"; import { intl, T } from "src/locale";
interface Props { interface Props {
initialValues: AccessListClient[]; initialValues: AccessListClient[];
@@ -65,8 +65,8 @@ export function AccessClientFields({ initialValues, name = "clients" }: Props) {
value={client.directive} value={client.directive}
onChange={(e) => handleChange(idx, "directive", e.target.value)} onChange={(e) => handleChange(idx, "directive", e.target.value)}
> >
<option value="allow">Allow</option> <option value="allow"><T id="action.allow" /></option>
<option value="deny">Deny</option> <option value="deny"><T id="action.deny" /></option>
</select> </select>
</span> </span>
<input <input
@@ -76,7 +76,7 @@ export function AccessClientFields({ initialValues, name = "clients" }: Props) {
autoComplete="off" autoComplete="off"
value={client.address} value={client.address}
onChange={(e) => handleChange(idx, "address", e.target.value)} onChange={(e) => handleChange(idx, "address", e.target.value)}
placeholder="192.168.1.100 or 192.168.1.0/24 or 2001:0db8::/32" placeholder={intl.formatMessage({ id: "access-list.rule-source.placeholder" })}
/> />
</div> </div>
</div> </div>
@@ -112,7 +112,7 @@ export function AccessClientFields({ initialValues, name = "clients" }: Props) {
value="deny" value="deny"
disabled disabled
> >
<option value="deny">Deny</option> <option value="deny"><T id="action.deny" /></option>
</select> </select>
</span> </span>
<input <input

View File

@@ -3,6 +3,7 @@ import { Field, useFormikContext } from "formik";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import Select, { type ActionMeta, components, type OptionProps } from "react-select"; import Select, { type ActionMeta, components, type OptionProps } from "react-select";
import type { AccessList } from "src/api/backend"; import type { AccessList } from "src/api/backend";
import { useLocaleState } from "src/context";
import { useAccessLists } from "src/hooks"; import { useAccessLists } from "src/hooks";
import { formatDateTime, intl, T } from "src/locale"; import { formatDateTime, intl, T } from "src/locale";
@@ -32,6 +33,7 @@ interface Props {
label?: string; label?: string;
} }
export function AccessField({ name = "accessListId", label = "access-list", id = "accessListId" }: Props) { export function AccessField({ name = "accessListId", label = "access-list", id = "accessListId" }: Props) {
const { locale } = useLocaleState();
const { isLoading, isError, error, data } = useAccessLists(["owner", "items", "clients"]); const { isLoading, isError, error, data } = useAccessLists(["owner", "items", "clients"]);
const { setFieldValue } = useFormikContext(); const { setFieldValue } = useFormikContext();
@@ -48,7 +50,7 @@ export function AccessField({ name = "accessListId", label = "access-list", id =
{ {
users: item?.items?.length, users: item?.items?.length,
rules: item?.clients?.length, rules: item?.clients?.length,
date: item?.createdOn ? formatDateTime(item?.createdOn) : "N/A", date: item?.createdOn ? formatDateTime(item?.createdOn, locale) : "N/A",
}, },
), ),
icon: <IconLock size={14} className="text-lime" />, icon: <IconLock size={14} className="text-lime" />,

View File

@@ -5,7 +5,7 @@ import { useState } from "react";
import Select, { type ActionMeta } from "react-select"; import Select, { type ActionMeta } from "react-select";
import type { DNSProvider } from "src/api/backend"; import type { DNSProvider } from "src/api/backend";
import { useDnsProviders } from "src/hooks"; import { useDnsProviders } from "src/hooks";
import { T } from "src/locale"; import { intl, T } from "src/locale";
import styles from "./DNSProviderFields.module.css"; import styles from "./DNSProviderFields.module.css";
interface DNSProviderOption { interface DNSProviderOption {
@@ -57,7 +57,7 @@ export function DNSProviderFields({ showBoundaryBox = false }: Props) {
id="dnsProvider" id="dnsProvider"
closeMenuOnSelect={true} closeMenuOnSelect={true}
isClearable={false} isClearable={false}
placeholder="Select a Provider..." placeholder={intl.formatMessage({ id: "certificates.dns.provider.placeholder" })}
isLoading={isLoading} isLoading={isLoading}
isSearchable isSearchable
onChange={handleChange} onChange={handleChange}
@@ -116,7 +116,7 @@ export function DNSProviderFields({ showBoundaryBox = false }: Props) {
type="number" type="number"
className="form-control" className="form-control"
min={0} min={0}
max={600} max={7200}
{...field} {...field}
/> />
<small className="text-muted"> <small className="text-muted">

View File

@@ -2,6 +2,7 @@ import { IconShield } from "@tabler/icons-react";
import { Field, useFormikContext } from "formik"; import { Field, useFormikContext } from "formik";
import Select, { type ActionMeta, components, type OptionProps } from "react-select"; import Select, { type ActionMeta, components, type OptionProps } from "react-select";
import type { Certificate } from "src/api/backend"; import type { Certificate } from "src/api/backend";
import { useLocaleState } from "src/context";
import { useCertificates } from "src/hooks"; import { useCertificates } from "src/hooks";
import { formatDateTime, intl, T } from "src/locale"; import { formatDateTime, intl, T } from "src/locale";
@@ -41,6 +42,7 @@ export function SSLCertificateField({
allowNew, allowNew,
forHttp = true, forHttp = true,
}: Props) { }: Props) {
const { locale } = useLocaleState();
const { isLoading, isError, error, data } = useCertificates(); const { isLoading, isError, error, data } = useCertificates();
const { values, setFieldValue } = useFormikContext(); const { values, setFieldValue } = useFormikContext();
const v: any = values || {}; const v: any = values || {};
@@ -75,7 +77,7 @@ export function SSLCertificateField({
data?.map((cert: Certificate) => ({ data?.map((cert: Certificate) => ({
value: cert.id, value: cert.id,
label: cert.niceName, label: cert.niceName,
subLabel: `${cert.provider === "letsencrypt" ? intl.formatMessage({ id: "lets-encrypt" }) : cert.provider} — ${intl.formatMessage({ id: "expires.on" }, { date: cert.expiresOn ? formatDateTime(cert.expiresOn) : "N/A" })}`, subLabel: `${cert.provider === "letsencrypt" ? intl.formatMessage({ id: "lets-encrypt" }) : cert.provider} — ${intl.formatMessage({ id: "expires.on" }, { date: cert.expiresOn ? formatDateTime(cert.expiresOn, locale) : "N/A" })}`,
icon: <IconShield size={14} className="text-pink" />, icon: <IconShield size={14} className="text-pink" />,
})) || []; })) || [];

View File

@@ -5,7 +5,11 @@ import { useTheme } from "src/hooks";
import { changeLocale, getFlagCodeForLocale, localeOptions, T } from "src/locale"; import { changeLocale, getFlagCodeForLocale, localeOptions, T } from "src/locale";
import styles from "./LocalePicker.module.css"; import styles from "./LocalePicker.module.css";
function LocalePicker() { interface Props {
menuAlign?: "start" | "end";
}
function LocalePicker({ menuAlign = "start" }: Props) {
const { locale, setLocale } = useLocaleState(); const { locale, setLocale } = useLocaleState();
const { getTheme } = useTheme(); const { getTheme } = useTheme();
@@ -23,9 +27,12 @@ function LocalePicker() {
<button type="button" className={cns} data-bs-toggle="dropdown"> <button type="button" className={cns} data-bs-toggle="dropdown">
<Flag countryCode={getFlagCodeForLocale(locale)} /> <Flag countryCode={getFlagCodeForLocale(locale)} />
</button> </button>
<div className="dropdown-menu"> <div
{localeOptions.map((item) => { className={cn("dropdown-menu", {
return ( "dropdown-menu-end": menuAlign === "end",
})}
>
{localeOptions.map((item: any) => (
<a <a
className="dropdown-item" className="dropdown-item"
href={`/locale/${item[0]}`} href={`/locale/${item[0]}`}
@@ -37,8 +44,7 @@ function LocalePicker() {
> >
<Flag countryCode={getFlagCodeForLocale(item[0])} /> <T id={`locale-${item[1]}`} /> <Flag countryCode={getFlagCodeForLocale(item[0])} /> <T id={`locale-${item[1]}`} />
</a> </a>
); ))}
})}
</div> </div>
</div> </div>
); );

View File

@@ -2,5 +2,5 @@ interface Props {
children: React.ReactNode; children: React.ReactNode;
} }
export function SiteContainer({ children }: Props) { export function SiteContainer({ children }: Props) {
return <div className="container-xl py-3">{children}</div>; return <div className="container-xl py-3 min-w-0 overflow-x-auto">{children}</div>;
} }

View File

@@ -1,8 +1,9 @@
import { useHealth } from "src/hooks"; import { useCheckVersion, useHealth } from "src/hooks";
import { T } from "src/locale"; import { T } from "src/locale";
export function SiteFooter() { export function SiteFooter() {
const health = useHealth(); const health = useHealth();
const { data: versionData } = useCheckVersion();
const getVersion = () => { const getVersion = () => {
if (!health.data) { if (!health.data) {
@@ -55,6 +56,19 @@ export function SiteFooter() {
{getVersion()}{" "} {getVersion()}{" "}
</a> </a>
</li> </li>
{versionData?.updateAvailable && versionData?.latest && (
<li className="list-inline-item">
<a
href={`https://github.com/NginxProxyManager/nginx-proxy-manager/releases/tag/${versionData.latest}`}
className="link-warning fw-bold"
target="_blank"
rel="noopener"
title={`New version ${versionData.latest} is available`}
>
<T id="update-available" data={{ latestVersion: versionData.latest }} />
</a>
</li>
)}
</ul> </ul>
</div> </div>
</div> </div>

View File

@@ -1,9 +1,9 @@
import { IconLock, IconLogout, IconUser } from "@tabler/icons-react"; import { IconLock, IconLogout, IconShieldLock, IconUser } from "@tabler/icons-react";
import { LocalePicker, NavLink, ThemeSwitcher } from "src/components"; import { LocalePicker, NavLink, ThemeSwitcher } from "src/components";
import { useAuthState } from "src/context"; import { useAuthState } from "src/context";
import { useUser } from "src/hooks"; import { useUser } from "src/hooks";
import { T } from "src/locale"; import { T } from "src/locale";
import { showChangePasswordModal, showUserModal } from "src/modals"; import { showChangePasswordModal, showTwoFactorModal, showUserModal } from "src/modals";
import styles from "./SiteHeader.module.css"; import styles from "./SiteHeader.module.css";
export function SiteHeader() { export function SiteHeader() {
@@ -25,7 +25,7 @@ export function SiteHeader() {
> >
<span className="navbar-toggler-icon" /> <span className="navbar-toggler-icon" />
</button> </button>
<div className="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3"> <div className="navbar-brand navbar-brand-autodark pe-0 pe-md-3">
<NavLink to="/"> <NavLink to="/">
<div className={styles.logo}> <div className={styles.logo}>
<img <img
@@ -48,11 +48,11 @@ export function SiteHeader() {
<ThemeSwitcher /> <ThemeSwitcher />
</div> </div>
</div> </div>
<div className="nav-item d-none d-md-flex me-3"> <div className="nav-item d-md-flex">
<div className="nav-item dropdown"> <div className="nav-item dropdown">
<a <a
href="/" href="/"
className="nav-link d-flex lh-1 p-0 px-2" className="nav-link d-flex lh-1"
data-bs-toggle="dropdown" data-bs-toggle="dropdown"
aria-label="Open user menu" aria-label="Open user menu"
> >
@@ -70,6 +70,22 @@ export function SiteHeader() {
</div> </div>
</a> </a>
<div className="dropdown-menu dropdown-menu-end dropdown-menu-arrow"> <div className="dropdown-menu dropdown-menu-end dropdown-menu-arrow">
<div className="d-md-none">
{/* biome-ignore lint/a11y/noStaticElementInteractions lint/a11y/useKeyWithClickEvents: This div is not interactive. */}
<div className="p-2 pb-1 pe-1 d-flex align-items-center" onClick={e => e.stopPropagation()}>
<div className="ps-2 pe-1 me-auto">
<div>{currentUser?.nickname}</div>
<div className="mt-1 small text-secondary text-nowrap">
<T id={isAdmin ? "role.admin" : "role.standard-user"} />
</div>
</div>
<div className="d-flex align-items-center">
<ThemeSwitcher className="me-n2" />
<LocalePicker menuAlign="end" />
</div>
</div>
<div className="dropdown-divider" />
</div>
<a <a
href="?" href="?"
className="dropdown-item" className="dropdown-item"
@@ -92,6 +108,17 @@ export function SiteHeader() {
<IconLock width={18} /> <IconLock width={18} />
<T id="user.change-password" /> <T id="user.change-password" />
</a> </a>
<a
href="?"
className="dropdown-item"
onClick={(e) => {
e.preventDefault();
showTwoFactorModal("me");
}}
>
<IconShieldLock width={18} />
<T id="user.two-factor" />
</a>
<div className="dropdown-divider" /> <div className="dropdown-divider" />
<a <a
href="?" href="?"

View File

@@ -176,17 +176,13 @@ const getMenuDropown = (item: MenuItem, onClick?: () => void) => {
}; };
export function SiteMenu() { export function SiteMenu() {
// This is hacky AF. But that's the price of using a non-react UI kit. const closeMenu = () => setTimeout(() => {
const closeMenus = () => { const navbarToggler = document.querySelector<HTMLElement>(".navbar-toggler");
const navMenus = document.querySelectorAll(".nav-item.dropdown"); const navbarMenu = document.querySelector("#navbar-menu");
navMenus.forEach((menu) => { if (navbarToggler && navbarMenu?.classList.contains("show")) {
menu.classList.remove("show"); navbarToggler.click();
const dropdown = menu.querySelector(".dropdown-menu");
if (dropdown) {
dropdown.classList.remove("show");
} }
}); }, 300);
};
return ( return (
<header className="navbar-expand-md"> <header className="navbar-expand-md">
@@ -198,7 +194,7 @@ export function SiteMenu() {
<ul className="navbar-nav"> <ul className="navbar-nav">
{menuItems.length > 0 && {menuItems.length > 0 &&
menuItems.map((item) => { menuItems.map((item) => {
return getMenuItem(item, closeMenus); return getMenuItem(item, closeMenu);
})} })}
</ul> </ul>
</div> </div>

View File

@@ -1,5 +1,6 @@
import cn from "classnames"; import cn from "classnames";
import { differenceInDays, isPast } from "date-fns"; import { differenceInDays, isPast } from "date-fns";
import { useLocaleState } from "src/context";
import { formatDateTime, parseDate } from "src/locale"; import { formatDateTime, parseDate } from "src/locale";
interface Props { interface Props {
@@ -8,6 +9,7 @@ interface Props {
highlistNearlyExpired?: boolean; highlistNearlyExpired?: boolean;
} }
export function DateFormatter({ value, highlightPast, highlistNearlyExpired }: Props) { export function DateFormatter({ value, highlightPast, highlistNearlyExpired }: Props) {
const { locale } = useLocaleState();
const d = parseDate(value); const d = parseDate(value);
const dateIsPast = d ? isPast(d) : false; const dateIsPast = d ? isPast(d) : false;
const days = d ? differenceInDays(d, new Date()) : 0; const days = d ? differenceInDays(d, new Date()) : 0;
@@ -15,5 +17,5 @@ export function DateFormatter({ value, highlightPast, highlistNearlyExpired }: P
"text-danger": highlightPast && dateIsPast, "text-danger": highlightPast && dateIsPast,
"text-warning": highlistNearlyExpired && !dateIsPast && days <= 30 && days >= 0, "text-warning": highlistNearlyExpired && !dateIsPast && days <= 30 && days >= 0,
}); });
return <span className={cl}>{formatDateTime(value)}</span>; return <span className={cl}>{formatDateTime(value, locale)}</span>;
} }

View File

@@ -1,5 +1,6 @@
import cn from "classnames"; import cn from "classnames";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { useLocaleState } from "src/context";
import { formatDateTime, T } from "src/locale"; import { formatDateTime, T } from "src/locale";
interface Props { interface Props {
@@ -10,8 +11,12 @@ interface Props {
color?: string; color?: string;
} }
const DomainLink = ({ domain, color }: { domain: string; color?: string }) => { const DomainLink = ({ domain, color }: { domain?: string; color?: string }) => {
// when domain contains a wildcard, make the link go nowhere. // when domain contains a wildcard, make the link go nowhere.
// Apparently the domain can be null or undefined sometimes.
// This try is just a safeguard to prevent the whole formatter from breaking.
if (!domain) return null;
try {
let onClick: ((e: React.MouseEvent) => void) | undefined; let onClick: ((e: React.MouseEvent) => void) | undefined;
if (domain.includes("*")) { if (domain.includes("*")) {
onClick = (e: React.MouseEvent) => e.preventDefault(); onClick = (e: React.MouseEvent) => e.preventDefault();
@@ -27,18 +32,23 @@ const DomainLink = ({ domain, color }: { domain: string; color?: string }) => {
{domain} {domain}
</a> </a>
); );
} catch {
return null;
}
}; };
export function DomainsFormatter({ domains, createdOn, niceName, provider, color }: Props) { export function DomainsFormatter({ domains, createdOn, niceName, provider, color }: Props) {
const { locale } = useLocaleState();
const elms: ReactNode[] = []; const elms: ReactNode[] = [];
if (domains.length === 0 && !niceName) {
if ((!domains || domains.length === 0) && !niceName) {
elms.push( elms.push(
<span key="nice-name" className="badge bg-danger-lt me-2"> <span key="nice-name" className="badge bg-danger-lt me-2">
Unknown Unknown
</span>, </span>,
); );
} }
if (niceName && provider !== "letsencrypt") { if (!domains || (niceName && provider !== "letsencrypt")) {
elms.push( elms.push(
<span key="nice-name" className="badge bg-info-lt me-2"> <span key="nice-name" className="badge bg-info-lt me-2">
{niceName} {niceName}
@@ -46,14 +56,16 @@ export function DomainsFormatter({ domains, createdOn, niceName, provider, color
); );
} }
if (domains) {
domains.map((domain: string) => elms.push(<DomainLink key={domain} domain={domain} color={color} />)); domains.map((domain: string) => elms.push(<DomainLink key={domain} domain={domain} color={color} />));
}
return ( return (
<div className="flex-fill"> <div className="flex-fill">
<div className="font-weight-medium">{...elms}</div> <div className="font-weight-medium">{...elms}</div>
{createdOn ? ( {createdOn ? (
<div className="text-secondary mt-1"> <div className="text-secondary mt-1">
<T id="created-on" data={{ date: formatDateTime(createdOn) }} /> <T id="created-on" data={{ date: formatDateTime(createdOn, locale) }} />
</div> </div>
) : null} ) : null}
</div> </div>

View File

@@ -1,6 +1,7 @@
import { IconArrowsCross, IconBolt, IconBoltOff, IconDisc, IconLock, IconShield, IconUser } from "@tabler/icons-react"; import { IconArrowsCross, IconBolt, IconBoltOff, IconDisc, IconLock, IconShield, IconUser } from "@tabler/icons-react";
import cn from "classnames"; import cn from "classnames";
import type { AuditLog } from "src/api/backend"; import type { AuditLog } from "src/api/backend";
import { useLocaleState } from "src/context";
import { formatDateTime, T } from "src/locale"; import { formatDateTime, T } from "src/locale";
const getEventValue = (event: AuditLog) => { const getEventValue = (event: AuditLog) => {
@@ -66,6 +67,7 @@ interface Props {
row: AuditLog; row: AuditLog;
} }
export function EventFormatter({ row }: Props) { export function EventFormatter({ row }: Props) {
const { locale } = useLocaleState();
return ( return (
<div className="flex-fill"> <div className="flex-fill">
<div className="font-weight-medium"> <div className="font-weight-medium">
@@ -73,7 +75,7 @@ export function EventFormatter({ row }: Props) {
<T id={`object.event.${row.action}`} tData={{ object: row.objectType }} /> <T id={`object.event.${row.action}`} tData={{ object: row.objectType }} />
&nbsp; &mdash; <span className="badge">{getEventValue(row)}</span> &nbsp; &mdash; <span className="badge">{getEventValue(row)}</span>
</div> </div>
<div className="text-secondary mt-1">{formatDateTime(row.createdOn)}</div> <div className="text-secondary mt-1">{formatDateTime(row.createdOn, locale)}</div>
</div> </div>
); );
} }

View File

@@ -1,3 +1,4 @@
import { useLocaleState } from "src/context";
import { formatDateTime, T } from "src/locale"; import { formatDateTime, T } from "src/locale";
interface Props { interface Props {
@@ -6,6 +7,7 @@ interface Props {
disabled?: boolean; disabled?: boolean;
} }
export function ValueWithDateFormatter({ value, createdOn, disabled }: Props) { export function ValueWithDateFormatter({ value, createdOn, disabled }: Props) {
const { locale } = useLocaleState();
return ( return (
<div className="flex-fill"> <div className="flex-fill">
<div className="font-weight-medium"> <div className="font-weight-medium">
@@ -13,7 +15,7 @@ export function ValueWithDateFormatter({ value, createdOn, disabled }: Props) {
</div> </div>
{createdOn ? ( {createdOn ? (
<div className={`text-secondary mt-1 ${disabled ? "text-red" : ""}`}> <div className={`text-secondary mt-1 ${disabled ? "text-red" : ""}`}>
<T id={disabled ? "disabled" : "created-on"} data={{ date: formatDateTime(createdOn) }} /> <T id={disabled ? "disabled" : "created-on"} data={{ date: formatDateTime(createdOn, locale) }} />
</div> </div>
) : null} ) : null}
</div> </div>

View File

@@ -12,10 +12,12 @@ interface TableLayoutProps<TFields> {
function TableLayout<TFields>(props: TableLayoutProps<TFields>) { function TableLayout<TFields>(props: TableLayoutProps<TFields>) {
const hasRows = props.tableInstance.getRowModel().rows.length > 0; const hasRows = props.tableInstance.getRowModel().rows.length > 0;
return ( return (
<div className="table-responsive">
<table className="table table-vcenter table-selectable mb-0"> <table className="table table-vcenter table-selectable mb-0">
{hasRows ? <TableHeader tableInstance={props.tableInstance} /> : null} {hasRows ? <TableHeader tableInstance={props.tableInstance} /> : null}
<TableBody {...props} /> <TableBody {...props} />
</table> </table>
</div>
); );
} }

View File

@@ -1,13 +1,28 @@
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import { createContext, type ReactNode, useContext, useState } from "react"; import { createContext, type ReactNode, useContext, useState } from "react";
import { useIntervalWhen } from "rooks"; import { useIntervalWhen } from "rooks";
import { getToken, loginAsUser, refreshToken, type TokenResponse } from "src/api/backend"; import {
getToken,
isTwoFactorChallenge,
loginAsUser,
refreshToken,
verify2FA,
type TokenResponse,
} from "src/api/backend";
import AuthStore from "src/modules/AuthStore"; import AuthStore from "src/modules/AuthStore";
// 2FA challenge state
export interface TwoFactorChallenge {
challengeToken: string;
}
// Context // Context
export interface AuthContextType { export interface AuthContextType {
authenticated: boolean; authenticated: boolean;
twoFactorChallenge: TwoFactorChallenge | null;
login: (username: string, password: string) => Promise<void>; login: (username: string, password: string) => Promise<void>;
verifyTwoFactor: (code: string) => Promise<void>;
cancelTwoFactor: () => void;
loginAs: (id: number) => Promise<void>; loginAs: (id: number) => Promise<void>;
logout: () => void; logout: () => void;
token?: string; token?: string;
@@ -24,17 +39,35 @@ interface Props {
function AuthProvider({ children, tokenRefreshInterval = 5 * 60 * 1000 }: Props) { function AuthProvider({ children, tokenRefreshInterval = 5 * 60 * 1000 }: Props) {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [authenticated, setAuthenticated] = useState(AuthStore.hasActiveToken()); const [authenticated, setAuthenticated] = useState(AuthStore.hasActiveToken());
const [twoFactorChallenge, setTwoFactorChallenge] = useState<TwoFactorChallenge | null>(null);
const handleTokenUpdate = (response: TokenResponse) => { const handleTokenUpdate = (response: TokenResponse) => {
AuthStore.set(response); AuthStore.set(response);
setAuthenticated(true); setAuthenticated(true);
setTwoFactorChallenge(null);
}; };
const login = async (identity: string, secret: string) => { const login = async (identity: string, secret: string) => {
const response = await getToken(identity, secret); const response = await getToken(identity, secret);
if (isTwoFactorChallenge(response)) {
setTwoFactorChallenge({ challengeToken: response.challengeToken });
return;
}
handleTokenUpdate(response); handleTokenUpdate(response);
}; };
const verifyTwoFactor = async (code: string) => {
if (!twoFactorChallenge) {
throw new Error("No 2FA challenge pending");
}
const response = await verify2FA(twoFactorChallenge.challengeToken, code);
handleTokenUpdate(response);
};
const cancelTwoFactor = () => {
setTwoFactorChallenge(null);
};
const loginAs = async (id: number) => { const loginAs = async (id: number) => {
const response = await loginAsUser(id); const response = await loginAsUser(id);
AuthStore.add(response); AuthStore.add(response);
@@ -69,7 +102,15 @@ function AuthProvider({ children, tokenRefreshInterval = 5 * 60 * 1000 }: Props)
true, true,
); );
const value = { authenticated, login, logout, loginAs }; const value = {
authenticated,
twoFactorChallenge,
login,
verifyTwoFactor,
cancelTwoFactor,
loginAs,
logout,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>; return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
} }

View File

@@ -4,6 +4,7 @@ export * from "./useAuditLog";
export * from "./useAuditLogs"; export * from "./useAuditLogs";
export * from "./useCertificate"; export * from "./useCertificate";
export * from "./useCertificates"; export * from "./useCertificates";
export * from "./useCheckVersion";
export * from "./useDeadHost"; export * from "./useDeadHost";
export * from "./useDeadHosts"; export * from "./useDeadHosts";
export * from "./useDnsProviders"; export * from "./useDnsProviders";

View File

@@ -0,0 +1,18 @@
import { useQuery } from "@tanstack/react-query";
import { checkVersion, type VersionCheckResponse } from "src/api/backend";
const fetchVersion = () => checkVersion();
const useCheckVersion = (options = {}) => {
return useQuery<VersionCheckResponse, Error>({
queryKey: ["version-check"],
queryFn: fetchVersion,
refetchOnWindowFocus: false,
retry: 5,
refetchInterval: 30 * 1000, // 30 seconds
staleTime: 5 * 60 * 1000, // 5 mins
...options,
});
};
export { fetchVersion, useCheckVersion };

84
frontend/src/locale/IntlProvider.tsx Normal file → Executable file
View File

@@ -1,41 +1,68 @@
import { createIntl, createIntlCache } from "react-intl"; import { createIntl, createIntlCache } from "react-intl";
import langDe from "./lang/de.json";
import langEn from "./lang/en.json"; import langEn from "./lang/en.json";
import langEs from "./lang/es.json"; import langEs from "./lang/es.json";
import langDe from "./lang/de.json"; import langGa from "./lang/ga.json";
import langIt from "./lang/it.json";
import langJa from "./lang/ja.json";
import langList from "./lang/lang-list.json"; import langList from "./lang/lang-list.json";
import langNl from "./lang/nl.json";
import langPl from "./lang/pl.json";
import langRu from "./lang/ru.json";
import langSk from "./lang/sk.json";
import langVi from "./lang/vi.json";
import langZh from "./lang/zh.json";
import langKo from "./lang/ko.json";
import langBg from "./lang/bg.json";
import langId from "./lang/id.json";
// first item of each array should be the language code, // first item of each array should be the language code,
// not the country code // not the country code
// Remember when adding to this list, also update check-locales.js script // Remember when adding to this list, also update check-locales.js script
const localeOptions = [ const localeOptions = [
["en", "en-US"], ["en", "en-US", langEn],
["es", "es-ES"], ["de", "de-DE", langDe],
["de", "de-DE"] ["es", "es-ES", langEs],
["ga", "ga-IE", langGa],
["ja", "ja-JP", langJa],
["it", "it-IT", langIt],
["nl", "nl-NL", langNl],
["pl", "pl-PL", langPl],
["ru", "ru-RU", langRu],
["sk", "sk-SK", langSk],
["vi", "vi-VN", langVi],
["zh", "zh-CN", langZh],
["ko", "ko-KR", langKo],
["bg", "bg-BG", langBg],
["id", "id-ID", langId],
]; ];
const loadMessages = (locale?: string): typeof langList & typeof langEn => { const loadMessages = (locale?: string): typeof langList & typeof langEn => {
const thisLocale = locale || "en"; const thisLocale = (locale || "en").slice(0, 2);
switch (thisLocale.slice(0, 2)) {
case "es": // ensure this lang exists in localeOptions above, otherwise fallback to en
return Object.assign({}, langList, langEs); if (thisLocale === "en" || !localeOptions.some(([code]) => code === thisLocale)) {
case "de":
return Object.assign({}, langList, langEn, langDe);
default:
return Object.assign({}, langList, langEn); return Object.assign({}, langList, langEn);
} }
return Object.assign({}, langList, langEn, localeOptions.find(([code]) => code === thisLocale)?.[2]);
}; };
const getFlagCodeForLocale = (locale?: string) => { const getFlagCodeForLocale = (locale?: string) => {
switch (locale) { const thisLocale = (locale || "en").slice(0, 2);
case "es-ES":
case "es": // only add to this if your flag is different from the locale code
return "ES"; const specialCases: Record<string, string> = {
case "de-DE": ja: "jp", // Japan
case "de": zh: "cn", // China
return "DE"; vi: "vn", // Vietnam
default: ko: "kr", // Korea
return "EN"; };
if (specialCases[thisLocale]) {
return specialCases[thisLocale].toUpperCase();
} }
return thisLocale.toUpperCase();
}; };
const getLocale = (short = false) => { const getLocale = (short = false) => {
@@ -56,10 +83,7 @@ const getLocale = (short = false) => {
const cache = createIntlCache(); const cache = createIntlCache();
const initialMessages = loadMessages(getLocale()); const initialMessages = loadMessages(getLocale());
let intl = createIntl( let intl = createIntl({ locale: getLocale(), messages: initialMessages }, cache);
{ locale: getLocale(), messages: initialMessages },
cache,
);
const changeLocale = (locale: string): void => { const changeLocale = (locale: string): void => {
const messages = loadMessages(locale); const messages = loadMessages(locale);
@@ -99,12 +123,6 @@ const T = ({
); );
}; };
export { console.log("L:", localeOptions);
localeOptions,
getFlagCodeForLocale, export { localeOptions, getFlagCodeForLocale, getLocale, createIntl, changeLocale, intl, T };
getLocale,
createIntl,
changeLocale,
intl,
T,
};

View File

@@ -39,8 +39,10 @@ not be complete by the time you're reading this:
- frontend/src/locale/src/[yourlang].json - frontend/src/locale/src/[yourlang].json
- frontend/src/locale/src/lang-list.json - frontend/src/locale/src/lang-list.json
- frontend/src/locale/src/HelpDoc/* - frontend/src/locale/src/HelpDoc/[yourlang]/*
- frontend/src/locale/src/HelpDoc/index.tsx
- frontend/src/locale/IntlProvider.tsx - frontend/src/locale/IntlProvider.tsx
- frontend/check-locales.cjs
## Checking for missing translations in languages ## Checking for missing translations in languages

View File

@@ -39,19 +39,19 @@ describe("DateFormatter", () => {
it("format date from iso date", () => { it("format date from iso date", () => {
const value = "2024-01-01T00:00:00.000Z"; const value = "2024-01-01T00:00:00.000Z";
const text = formatDateTime(value); const text = formatDateTime(value);
expect(text).toBe("Monday, 01/01/2024, 12:00:00 am"); expect(text).toBe("1 Jan 2024, 12:00:00 am");
}); });
it("format date from unix timestamp number", () => { it("format date from unix timestamp number", () => {
const value = 1762476112; const value = 1762476112;
const text = formatDateTime(value); const text = formatDateTime(value);
expect(text).toBe("Friday, 07/11/2025, 12:41:52 am"); expect(text).toBe("7 Nov 2025, 12:41:52 am");
}); });
it("format date from unix timestamp string", () => { it("format date from unix timestamp string", () => {
const value = "1762476112"; const value = "1762476112";
const text = formatDateTime(value); const text = formatDateTime(value);
expect(text).toBe("Friday, 07/11/2025, 12:41:52 am"); expect(text).toBe("7 Nov 2025, 12:41:52 am");
}); });
it("catch bad format from string", () => { it("catch bad format from string", () => {

View File

@@ -1,4 +1,9 @@
import { fromUnixTime, intlFormat, parseISO } from "date-fns"; import {
fromUnixTime,
type IntlFormatFormatOptions,
intlFormat,
parseISO,
} from "date-fns";
const isUnixTimestamp = (value: unknown): boolean => { const isUnixTimestamp = (value: unknown): boolean => {
if (typeof value !== "number" && typeof value !== "string") return false; if (typeof value !== "number" && typeof value !== "string") return false;
@@ -20,20 +25,19 @@ const parseDate = (value: string | number): Date | null => {
} }
}; };
const formatDateTime = (value: string | number): string => { const formatDateTime = (value: string | number, locale = "en-US"): string => {
const d = parseDate(value); const d = parseDate(value);
if (!d) return `${value}`; if (!d) return `${value}`;
try { try {
return intlFormat(d, { return intlFormat(
weekday: "long", d,
year: "numeric", {
month: "numeric", dateStyle: "medium",
day: "numeric", timeStyle: "medium",
hour: "numeric", hourCycle: "h12",
minute: "numeric", } as IntlFormatFormatOptions,
second: "numeric", { locale },
hour12: true, );
});
} catch { } catch {
return `${value}`; return `${value}`;
} }

View File

@@ -1,215 +0,0 @@
{
"access-list": "Zugriffsliste",
"access-list.access-count": "{count} {count, plural, one {Regel} other {Regeln}}",
"access-list.auth-count": "{count} {count, plural, one {User} other {Users}}",
"access-list.help-rules-last": "Wenn mindestens eine Regel vorhanden ist, wird diese Regel zum Ablehnen aller Anfragen als letzte hinzugefügt.",
"access-list.help.rules-order": "Beachten Sie, dass die Anweisungen „Erlauben“ und „Verbieten“ in der Reihenfolge ihrer Definition angewendet werden.",
"access-list.pass-auth": "Authentifizierung an Upstream weiterleiten",
"access-list.public": "Öffentlich",
"access-list.public.subtitle": "Keine Authentifizierung erforderlich",
"access-list.satisfy-any": "Satisfy Any",
"access-list.subtitle": "{users} {users, plural, one {User} other {Users}}, {rules} {rules, plural, one {Regel} other {Regeln}} - Erstellt: {date}",
"access-lists": "Zugrifflisten",
"action.add": "Hinzufügen",
"action.add-location": "Pfad Hinzufügen",
"action.close": "Schließen",
"action.delete": "Löschen",
"action.disable": "Deaktivieren",
"action.download": "Herunterladen",
"action.edit": "Bearbeiten",
"action.enable": "Aktivieren",
"action.permissions": "Berechtigungen",
"action.renew": "Erneuert",
"action.view-details": "Details",
"auditlogs": "Protokoll",
"cancel": "Abbrechen",
"certificate": "Zertifikat",
"certificate.custom-certificate": "Zertifikat",
"certificate.custom-certificate-key": "Privater Schlüssel",
"certificate.custom-intermediate": "Zwischen Zertifikat",
"certificate.in-use": "In Benutzung",
"certificate.none.subtitle": "Kein Zertifikat zugewiesen",
"certificate.none.subtitle.for-http": "Dieser Host verwendet kein HTTPS.",
"certificate.none.title": "Kein",
"certificate.not-in-use": "Nicht in Benutzung",
"certificates": "Zertifikate",
"certificates.custom": "Benutzerdefiniertes Zertifikat",
"certificates.custom.warning": "Mit einem Passwort geschützte Schlüsseldateien werden nicht unterstützt.",
"certificates.dns.credentials": "Inhalt der Anmeldedaten-Datei",
"certificates.dns.credentials-note": "Dieses Plugin erfordert eine Konfigurationsdatei, die einen API-Token oder andere Anmeldedaten für Ihren Anbieter enthält.",
"certificates.dns.credentials-warning": "Diese Daten werden als Klartext in der Datenbank und in einer Datei gespeichert!",
"certificates.dns.propagation-seconds": "Wartzeit in Sekunden",
"certificates.dns.propagation-seconds-note": "Leer lassen um die Standardwartezeit des Plugins zu nutzen",
"certificates.dns.provider": "DNS Provider",
"certificates.dns.warning": "Dieser Abschnitt erfordert einige Kenntnisse über Certbot und seine DNS-Plugins. Bitte konsultieren Sie die jeweilige Plugin-Dokumentation.",
"certificates.http.reachability-404": "Unter dieser Domain wurde ein Server gefunden, aber es scheint sich nicht um Nginx Proxy Manager zu handeln. Bitte stellen Sie sicher, dass Ihre Domain auf die IP-Adresse verweist, unter der Ihre NPM-Instanz ausgeführt wird.",
"certificates.http.reachability-failed-to-check": "Die Erreichbarkeit konnte aufgrund eines Kommunikationsfehlers mit site24x7.com nicht überprüft werden.",
"certificates.http.reachability-not-resolved": "Unter dieser Domain ist kein Server verfügbar. Bitte stellen Sie sicher, dass Ihre Domain existiert und auf die IP-Adresse verweist, unter der Ihre NPM-Instanz läuft, und dass gegebenenfalls Port 80 in Ihrem Router weitergeleitet wird.",
"certificates.http.reachability-ok": "Ihr Server ist erreichbar und die Erstellung von Zertifikaten sollte möglich sein.",
"certificates.http.reachability-other": "Unter dieser Domain wurde ein Server gefunden, der jedoch einen unerwarteten Statuscode {code} zurückgegeben hat. Handelt es sich um den NPM-Server? Bitte stellen Sie sicher, dass Ihre Domain auf die IP-Adresse verweist, unter der Ihre NPM-Instanz ausgeführt wird.",
"certificates.http.reachability-wrong-data": "Unter dieser Domain wurde ein Server gefunden, der jedoch unerwartete Daten zurückgegeben hat. Handelt es sich um den NPM-Server? Bitte stellen Sie sicher, dass Ihre Domain auf die IP-Adresse verweist, unter der Ihre NPM-Instanz ausgeführt wird.",
"certificates.http.test-results": "Test Ergeniss",
"certificates.http.warning": "Diese Domänen müssen bereits so konfiguriert sein, dass sie auf diese Installation verweisen.",
"certificates.request.subtitle": "Über Let's Encrypt",
"certificates.request.title": "Anfordern eines neuen Zertifikates",
"column.access": "Zugriff",
"column.authorization": "Genehmigung",
"column.authorizations": "Genehmigungen",
"column.custom-locations": "Benutzerdefinierte Pfad",
"column.destination": "Ziel",
"column.details": "Details",
"column.email": "Email",
"column.event": "Ereignis",
"column.expires": "Verfällt am",
"column.http-code": "HTTP Code",
"column.incoming-port": "Eingehender Port",
"column.name": "Name",
"column.protocol": "Protokoll",
"column.provider": "Provider",
"column.roles": "Rollen",
"column.rules": "Regeln",
"column.satisfy": "Satisfy",
"column.satisfy-all": "Alle",
"column.satisfy-any": "Jeder",
"column.scheme": "Schema",
"column.source": "Quelle",
"column.ssl": "SSL",
"column.status": "Status",
"created-on": "Erstelldatum: {date}",
"dashboard": "Dashboard",
"dead-host": "404 Host",
"dead-hosts": "404 Hosts",
"dead-hosts.count": "{count} {count, plural, one {404 Host} other {404 Hosts}}",
"disabled": "Deaktiviert",
"domain-names": "Domain Names",
"domain-names.max": "{count} Maximale Anzahl von Domainnamen",
"domain-names.placeholder": "Eintragen der Domain...",
"domain-names.wildcards-not-permitted": "Wildcards sind für diesen Typ nicht zulässig.",
"domain-names.wildcards-not-supported": "Wildcards werden für diese Zertifizierungsstelle nicht unterstützt.",
"domains.force-ssl": "Erzwinge SSL",
"domains.hsts-enabled": "HSTS aktiviert",
"domains.hsts-subdomains": "HSTS Sub-domains",
"domains.http2-support": "HTTP/2 Support",
"domains.use-dns": "Nutze DNS Challenge",
"email-address": "Email Addresse",
"empty-search": "Keine Ergebnisse gefunden",
"empty-subtitle": "Warum erstellen Sie nicht eine?",
"enabled": "aktiviert",
"error.access.at-least-one": "Entweder eine Genehmigung oder eine Zugriffsregel ist erforderlich.",
"error.access.duplicate-usernames": "Autorisierung Benutzernamen müssen eindeutig sein",
"error.invalid-auth": "Ungültige E-Mail-Adresse oder Passwort",
"error.invalid-domain": "Ungültige Domain: {domain}",
"error.invalid-email": "Ungültige E-Mail-Adresse",
"error.max-character-length": "Die maximale Länge beträgt {max} Zeichen{max, plural, one {} other {s}}",
"error.max-domains": "Zu viele Domains, maximal sind {max}",
"error.maximum": "Maximum ist {max}",
"error.min-character-length": "Die minimale Länge beträgt {min} Zeichen{min, plural, one {} other {s}}",
"error.minimum": "Minimum ist {min}",
"error.passwords-must-match": "Passwörter müssen übereinstimmen",
"error.required": "Dies ist erforderlich.",
"expires.on": "Ablauf am: {date}",
"footer.github-fork": "Fork me on Github",
"host.flags.block-exploits": "Gängige Exploits blockieren",
"host.flags.cache-assets": "Cache Assets",
"host.flags.preserve-path": "Pfad beibehalten",
"host.flags.protocols": "Protokole",
"host.flags.websockets-upgrade": "Websockets Support",
"host.forward-port": "Forward Port",
"host.forward-scheme": "Schema",
"hosts": "Hosts",
"http-only": "HTTP Only",
"lets-encrypt": "Let's Encrypt",
"lets-encrypt-via-dns": "Let's Encrypt via DNS",
"lets-encrypt-via-http": "Let's Encrypt via HTTP",
"loading": "Laden…",
"login.title": "Account Login",
"nginx-config.label": "Benutzerdefinierte Nginx Konfiguration",
"nginx-config.placeholder": "# Geben Sie hier Ihre benutzerdefinierte Nginx-Konfiguration auf eigene Gefahr ein!",
"no-permission-error": "Sie haben keinen Zugriff, um dies anzuzeigen.",
"notfound.action": "Take me home",
"notfound.content": "We are sorry but the page you are looking for was not found",
"notfound.title": "Oops… You just found an error page",
"notification.error": "Error",
"notification.object-deleted": "{object} wurde gelöscht",
"notification.object-disabled": "{object} wurde deaktiviert",
"notification.object-enabled": "{object} wurde aktiviert",
"notification.object-renewed": "{object} wurde erneuert",
"notification.object-saved": "{object} wurde gespeichert",
"notification.success": "Erfolgreich",
"object.actions-title": "{object} #{id}",
"object.add": "{object} hinzufügen",
"object.delete": "{object} löschen",
"object.delete.content": "Bist du dir sicher das du diese(n) {object} löschen möchtest?",
"object.edit": "{object} bearbeiten",
"object.empty": "Keine {objects} vorhanden",
"object.event.created": "{object} erstellt",
"object.event.deleted": "{object} gelöscht",
"object.event.disabled": "{object} deaktiviert",
"object.event.enabled": "{object} aktiviert",
"object.event.renewed": "{object} erneuert",
"object.event.updated": "{object} aktualisiert",
"offline": "Offline",
"online": "Online",
"options": "Optionen",
"password": "Passwort",
"password.generate": "Zufälliges Passwort generieren",
"password.hide": "Passwort verstecken",
"password.show": "Passwort anzeigen",
"permissions.hidden": "Versteckt",
"permissions.manage": "Verwalten",
"permissions.view": "Nur anzeigen",
"permissions.visibility.all": "Alle Elemente",
"permissions.visibility.title": "Objekt Sichtbarkeit",
"permissions.visibility.user": "Nur erstellte Elemente",
"proxy-host": "Proxy Host",
"proxy-host.forward-host": "Forward Hostname / IP",
"proxy-hosts": "Proxy Hosts",
"proxy-hosts.count": "{count} {count, plural, one {Proxy Host} other {Proxy Hosts}}",
"public": "Öffentlich",
"redirection-host": "Redirection Host",
"redirection-host.forward-domain": "Forward Domain",
"redirection-host.forward-http-code" : "HTTP Code",
"redirection-hosts": "Redirection Hosts",
"redirection-hosts.count": "{count} {count, plural, one {Redirection Host} other {Redirection Hosts}}",
"role.admin": "Administrator",
"role.standard-user": "Standard User",
"save": "Speichern",
"setting": "Einstellung",
"settings": "Einstellungen",
"settings.default-site": "Standard Seite",
"settings.default-site.404": "404 Page",
"settings.default-site.444": "No Response (444)",
"settings.default-site.congratulations": "Willkommensseite",
"settings.default-site.description": "Was angezeigt wird, wenn der Nginx eine unbekannte Webseitenanfrage bekommt",
"settings.default-site.html": "Benutzerdefinierte HTML",
"settings.default-site.html.placeholder": "<!-- Geben Sie hier Ihren benutzerdefinierten HTML-Inhalt ein. -->",
"settings.default-site.redirect": "Weiterleitung",
"setup.preamble": "Beginnen Sie mit der Erstellung Ihres Administratorkontos.",
"setup.title": "Willkommen!",
"sign-in": "Login",
"ssl-certificate": "SSL Zertifikate",
"stream": "Stream",
"stream.forward-host": "Forward Host",
"stream.incoming-port": "Incoming Port",
"streams": "Streams",
"streams.count": "{count} {count, plural, one {Stream} other {Streams}}",
"streams.tcp": "TCP",
"streams.udp": "UDP",
"test": "Test",
"user": "User",
"user.change-password": "Passwort ändern",
"user.confirm-password": "Passwort wiederholen",
"user.current-password": "Aktuelles Passwort",
"user.edit-profile": "Profil bearbeiten",
"user.full-name": "Name",
"user.login-as": "Einloggen als {name}",
"user.logout": "Ausloggen",
"user.new-password": "Neues Password",
"user.nickname": "Nickname",
"user.set-password": "Passwort setzen",
"user.set-permissions": "Berechtigungen für {name} setzen",
"user.switch-dark": "Zum Dark Mode wechseln",
"user.switch-light": "Zum Light Mode wechslen",
"username": "Benutzername",
"users": "Benutzer"
}

View File

@@ -1,216 +0,0 @@
{
"access-list": "Access List",
"access-list.access-count": "{count} {count, plural, one {Rule} other {Rules}}",
"access-list.auth-count": "{count} {count, plural, one {User} other {Users}}",
"access-list.help-rules-last": "When at least 1 rule exists, this deny all rule will be added last",
"access-list.help.rules-order": "Note that the allow and deny directives will be applied in the order they are defined.",
"access-list.pass-auth": "Pass Auth to Upstream",
"access-list.public": "Publicly Accessible",
"access-list.public.subtitle": "No basic auth required",
"access-list.satisfy-any": "Satisfy Any",
"access-list.subtitle": "{users} {users, plural, one {User} other {Users}}, {rules} {rules, plural, one {Rule} other {Rules}} - Created: {date}",
"access-lists": "Access Lists",
"action.add": "Add",
"action.add-location": "Add Location",
"action.close": "Close",
"action.delete": "Delete",
"action.disable": "Disable",
"action.download": "Download",
"action.edit": "Edit",
"action.enable": "Enable",
"action.permissions": "Permissions",
"action.renew": "Renew",
"action.view-details": "View Details",
"auditlogs": "Audit Logs",
"cancel": "Cancel",
"certificate": "Certificate",
"certificate.custom-certificate": "Certificate",
"certificate.custom-certificate-key": "Certificate Key",
"certificate.custom-intermediate": "Intermediate Certificate",
"certificate.in-use": "In Use",
"certificate.none.subtitle": "No certificate assigned",
"certificate.none.subtitle.for-http": "This host will not use HTTPS",
"certificate.none.title": "None",
"certificate.not-in-use": "Not Used",
"certificate.renew": "Renew Certificate",
"certificates": "Certificates",
"certificates.custom": "Custom Certificate",
"certificates.custom.warning": "Key files protected with a passphrase are not supported.",
"certificates.dns.credentials": "Credentials File Content",
"certificates.dns.credentials-note": "This plugin requires a configuration file containing an API token or other credentials for your provider",
"certificates.dns.credentials-warning": "This data will be stored as plaintext in the database and in a file!",
"certificates.dns.propagation-seconds": "Propagation Seconds",
"certificates.dns.propagation-seconds-note": "Leave empty to use the plugins default value. Number of seconds to wait for DNS propagation.",
"certificates.dns.provider": "DNS Provider",
"certificates.dns.warning": "This section requires some knowledge about Certbot and its DNS plugins. Please consult the respective plugins documentation.",
"certificates.http.reachability-404": "There is a server found at this domain but it does not seem to be Nginx Proxy Manager. Please make sure your domain points to the IP where your NPM instance is running.",
"certificates.http.reachability-failed-to-check": "Failed to check the reachability due to a communication error with site24x7.com.",
"certificates.http.reachability-not-resolved": "There is no server available at this domain. Please make sure your domain exists and points to the IP where your NPM instance is running and if necessary port 80 is forwarded in your router.",
"certificates.http.reachability-ok": "Your server is reachable and creating certificates should be possible.",
"certificates.http.reachability-other": "There is a server found at this domain but it returned an unexpected status code {code}. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running.",
"certificates.http.reachability-wrong-data": "There is a server found at this domain but it returned an unexpected data. Is it the NPM server? Please make sure your domain points to the IP where your NPM instance is running.",
"certificates.http.test-results": "Test Results",
"certificates.http.warning": "These domains must be already configured to point to this installation.",
"certificates.request.subtitle": "with Let's Encrypt",
"certificates.request.title": "Request a new Certificate",
"column.access": "Access",
"column.authorization": "Authorization",
"column.authorizations": "Authorizations",
"column.custom-locations": "Custom Locations",
"column.destination": "Destination",
"column.details": "Details",
"column.email": "Email",
"column.event": "Event",
"column.expires": "Expires",
"column.http-code": "HTTP Code",
"column.incoming-port": "Incoming Port",
"column.name": "Name",
"column.protocol": "Protocol",
"column.provider": "Provider",
"column.roles": "Roles",
"column.rules": "Rules",
"column.satisfy": "Satisfy",
"column.satisfy-all": "All",
"column.satisfy-any": "Any",
"column.scheme": "Scheme",
"column.source": "Source",
"column.ssl": "SSL",
"column.status": "Status",
"created-on": "Created: {date}",
"dashboard": "Dashboard",
"dead-host": "404 Host",
"dead-hosts": "404 Hosts",
"dead-hosts.count": "{count} {count, plural, one {404 Host} other {404 Hosts}}",
"disabled": "Disabled",
"domain-names": "Domain Names",
"domain-names.max": "{count} domain names maximum",
"domain-names.placeholder": "Start typing to add domain...",
"domain-names.wildcards-not-permitted": "Wildcards not permitted for this type",
"domain-names.wildcards-not-supported": "Wildcards not supported for this CA",
"domains.force-ssl": "Force SSL",
"domains.hsts-enabled": "HSTS Enabled",
"domains.hsts-subdomains": "HSTS Sub-domains",
"domains.http2-support": "HTTP/2 Support",
"domains.use-dns": "Use DNS Challenge",
"email-address": "Email address",
"empty-search": "No results found",
"empty-subtitle": "Why don't you create one?",
"enabled": "Enabled",
"error.access.at-least-one": "Either one Authorization or one Access Rule is required",
"error.access.duplicate-usernames": "Authorization Usernames must be unique",
"error.invalid-auth": "Invalid email or password",
"error.invalid-domain": "Invalid domain: {domain}",
"error.invalid-email": "Invalid email address",
"error.max-character-length": "Maximum length is {max} character{max, plural, one {} other {s}}",
"error.max-domains": "Too many domains, max is {max}",
"error.maximum": "Maximum is {max}",
"error.min-character-length": "Minimum length is {min} character{min, plural, one {} other {s}}",
"error.minimum": "Minimum is {min}",
"error.passwords-must-match": "Passwords must match",
"error.required": "This is required",
"expires.on": "Expires: {date}",
"footer.github-fork": "Fork me on Github",
"host.flags.block-exploits": "Block Common Exploits",
"host.flags.cache-assets": "Cache Assets",
"host.flags.preserve-path": "Preserve Path",
"host.flags.protocols": "Protocols",
"host.flags.websockets-upgrade": "Websockets Support",
"host.forward-port": "Forward Port",
"host.forward-scheme": "Scheme",
"hosts": "Hosts",
"http-only": "HTTP Only",
"lets-encrypt": "Let's Encrypt",
"lets-encrypt-via-dns": "Let's Encrypt via DNS",
"lets-encrypt-via-http": "Let's Encrypt via HTTP",
"loading": "Loading…",
"login.title": "Login to your account",
"nginx-config.label": "Custom Nginx Configuration",
"nginx-config.placeholder": "# Enter your custom Nginx configuration here at your own risk!",
"no-permission-error": "You do not have access to view this.",
"notfound.action": "Take me home",
"notfound.content": "We are sorry but the page you are looking for was not found",
"notfound.title": "Oops… You just found an error page",
"notification.error": "Error",
"notification.object-deleted": "{object} has been deleted",
"notification.object-disabled": "{object} has been disabled",
"notification.object-enabled": "{object} has been enabled",
"notification.object-renewed": "{object} has been renewed",
"notification.object-saved": "{object} has been saved",
"notification.success": "Success",
"object.actions-title": "{object} #{id}",
"object.add": "Add {object}",
"object.delete": "Delete {object}",
"object.delete.content": "Are you sure you want to delete this {object}?",
"object.edit": "Edit {object}",
"object.empty": "There are no {objects}",
"object.event.created": "Created {object}",
"object.event.deleted": "Deleted {object}",
"object.event.disabled": "Disabled {object}",
"object.event.enabled": "Enabled {object}",
"object.event.renewed": "Renewed {object}",
"object.event.updated": "Updated {object}",
"offline": "Offline",
"online": "Online",
"options": "Options",
"password": "Password",
"password.generate": "Generate random password",
"password.hide": "Hide Password",
"password.show": "Show Password",
"permissions.hidden": "Hidden",
"permissions.manage": "Manage",
"permissions.view": "View Only",
"permissions.visibility.all": "All Items",
"permissions.visibility.title": "Item Visibility",
"permissions.visibility.user": "Created Items Only",
"proxy-host": "Proxy Host",
"proxy-host.forward-host": "Forward Hostname / IP",
"proxy-hosts": "Proxy Hosts",
"proxy-hosts.count": "{count} {count, plural, one {Proxy Host} other {Proxy Hosts}}",
"public": "Public",
"redirection-host": "Redirection Host",
"redirection-host.forward-domain": "Forward Domain",
"redirection-host.forward-http-code": "HTTP Code",
"redirection-hosts": "Redirection Hosts",
"redirection-hosts.count": "{count} {count, plural, one {Redirection Host} other {Redirection Hosts}}",
"role.admin": "Administrator",
"role.standard-user": "Standard User",
"save": "Save",
"setting": "Setting",
"settings": "Settings",
"settings.default-site": "Default Site",
"settings.default-site.404": "404 Page",
"settings.default-site.444": "No Response (444)",
"settings.default-site.congratulations": "Congratulations Page",
"settings.default-site.description": "What to show when Nginx is hit with an unknown Host",
"settings.default-site.html": "Custom HTML",
"settings.default-site.html.placeholder": "<!-- Enter your custom HTML content here -->",
"settings.default-site.redirect": "Redirect",
"setup.preamble": "Get started by creating your admin account.",
"setup.title": "Welcome!",
"sign-in": "Sign in",
"ssl-certificate": "SSL Certificate",
"stream": "Stream",
"stream.forward-host": "Forward Host",
"stream.incoming-port": "Incoming Port",
"streams": "Streams",
"streams.count": "{count} {count, plural, one {Stream} other {Streams}}",
"streams.tcp": "TCP",
"streams.udp": "UDP",
"test": "Test",
"user": "User",
"user.change-password": "Change Password",
"user.confirm-password": "Confirm Password",
"user.current-password": "Current Password",
"user.edit-profile": "Edit Profile",
"user.full-name": "Full Name",
"user.login-as": "Sign in as {name}",
"user.logout": "Logout",
"user.new-password": "New Password",
"user.nickname": "Nickname",
"user.set-password": "Set Password",
"user.set-permissions": "Set Permissions for {name}",
"user.switch-dark": "Switch to Dark mode",
"user.switch-light": "Switch to Light mode",
"username": "Username",
"users": "Users"
}

View File

@@ -1,216 +0,0 @@
{
"access-list": "Lista de Acceso",
"access-list.access-count": "{count} {count, plural, one {Regla} other {Reglas}}",
"access-list.auth-count": "{count} {count, plural, one {Usuario} other {Usuarios}}",
"access-list.help-rules-last": "Cuando exista al menos 1 regla, esta regla de denegar todo se añadirá al final",
"access-list.help.rules-order": "Ten en cuenta que las directivas de permitir y denegar se aplicarán en el orden en que estén definidas.",
"access-list.pass-auth": "Pasar Autenticación al Upstream",
"access-list.public": "Accesible Públicamente",
"access-list.public.subtitle": "No se requiere autenticación básica",
"access-list.satisfy-any": "Satisfacer Cualquiera",
"access-list.subtitle": "{users} {users, plural, one {Usuario} other {Usuarios}}, {rules} {rules, plural, one {Regla} other {Reglas}} - Creado: {date}",
"access-lists": "Listas de Acceso",
"action.add": "Añadir",
"action.add-location": "Añadir Ubicación",
"action.close": "Cerrar",
"action.delete": "Eliminar",
"action.disable": "Deshabilitar",
"action.download": "Descargar",
"action.edit": "Editar",
"action.enable": "Habilitar",
"action.permissions": "Permisos",
"action.renew": "Renovar",
"action.view-details": "Ver Detalles",
"auditlogs": "Registros de Auditoría",
"cancel": "Cancelar",
"certificate": "Certificado",
"certificate.custom-certificate": "Certificado",
"certificate.custom-certificate-key": "Clave del Certificado",
"certificate.custom-intermediate": "Certificado Intermedio",
"certificate.in-use": "En Uso",
"certificate.none.subtitle": "Sin certificado asignado",
"certificate.none.subtitle.for-http": "Este host no usará HTTPS",
"certificate.none.title": "Ninguno",
"certificate.not-in-use": "Sin Usar",
"certificate.renew": "Renovar Certificado",
"certificates": "Certificados",
"certificates.custom": "Certificado Personalizado",
"certificates.custom.warning": "No se admiten archivos de claves protegidos con contraseña.",
"certificates.dns.credentials": "Contenido del Archivo de Credenciales",
"certificates.dns.credentials-note": "Este plugin requiere un archivo de configuración que contenga un token de API u otras credenciales para tu proveedor",
"certificates.dns.credentials-warning": "¡Estos datos se almacenarán como texto plano en la base de datos y en un archivo!",
"certificates.dns.propagation-seconds": "Segundos de Propagación",
"certificates.dns.propagation-seconds-note": "Dejar vacío para usar el valor predeterminado del plugin. Número de segundos a esperar para la propagación DNS.",
"certificates.dns.provider": "Proveedor DNS",
"certificates.dns.warning": "Esta sección requiere algunos conocimientos sobre Certbot y sus plugins DNS. Consulta la documentación de los plugins respectivos.",
"certificates.http.reachability-404": "Se encontró un servidor en este dominio pero no parece ser Nginx Proxy Manager. Asegúrate de que tu dominio apunte a la IP donde se está ejecutando tu instancia de NPM.",
"certificates.http.reachability-failed-to-check": "No se pudo verificar la accesibilidad debido a un error de comunicación con site24x7.com.",
"certificates.http.reachability-not-resolved": "No hay ningún servidor disponible en este dominio. Asegúrate de que tu dominio existe y apunta a la IP donde se está ejecutando tu instancia de NPM y, si es necesario, que el puerto 80 esté redirigido en tu router.",
"certificates.http.reachability-ok": "Tu servidor es accesible y debería ser posible crear certificados.",
"certificates.http.reachability-other": "Se encontró un servidor en este dominio pero devolvió un código de estado inesperado {code}. ¿Es el servidor NPM? Asegúrate de que tu dominio apunte a la IP donde se está ejecutando tu instancia de NPM.",
"certificates.http.reachability-wrong-data": "Se encontró un servidor en este dominio pero devolvió datos inesperados. ¿Es el servidor NPM? Asegúrate de que tu dominio apunte a la IP donde se está ejecutando tu instancia de NPM.",
"certificates.http.test-results": "Resultados de la Prueba",
"certificates.http.warning": "Estos dominios ya deben estar configurados para apuntar a esta instalación.",
"certificates.request.subtitle": "con Let's Encrypt",
"certificates.request.title": "Solicitar un nuevo Certificado",
"column.access": "Acceso",
"column.authorization": "Autorización",
"column.authorizations": "Autorizaciones",
"column.custom-locations": "Ubicaciones Personalizadas",
"column.destination": "Destino",
"column.details": "Detalles",
"column.email": "Correo Electrónico",
"column.event": "Evento",
"column.expires": "Expira",
"column.http-code": "Código HTTP",
"column.incoming-port": "Puerto de Entrada",
"column.name": "Nombre",
"column.protocol": "Protocolo",
"column.provider": "Proveedor",
"column.roles": "Roles",
"column.rules": "Reglas",
"column.satisfy": "Satisfacer",
"column.satisfy-all": "Todo",
"column.satisfy-any": "Cualquiera",
"column.scheme": "Esquema",
"column.source": "Origen",
"column.ssl": "SSL",
"column.status": "Estado",
"created-on": "Creado: {date}",
"dashboard": "Panel de Control",
"dead-host": "Host 404",
"dead-hosts": "Hosts 404",
"dead-hosts.count": "{count} {count, plural, one {Host 404} other {Hosts 404}}",
"disabled": "Deshabilitado",
"domain-names": "Nombres de Dominio",
"domain-names.max": "{count} nombres de dominio como máximo",
"domain-names.placeholder": "Comienza a escribir para añadir dominio...",
"domain-names.wildcards-not-permitted": "No se permiten comodines para este tipo",
"domain-names.wildcards-not-supported": "No se admiten comodines para esta CA",
"domains.force-ssl": "Forzar SSL",
"domains.hsts-enabled": "HSTS Habilitado",
"domains.hsts-subdomains": "HSTS en Subdominios",
"domains.http2-support": "Soporte HTTP/2",
"domains.use-dns": "Usar Desafío DNS",
"email-address": "Dirección de correo electrónico",
"empty-search": "No se encontraron resultados",
"empty-subtitle": "¿Por qué no creas uno?",
"enabled": "Habilitado",
"error.access.at-least-one": "Se requiere al menos una Autorización o una Regla de Acceso",
"error.access.duplicate-usernames": "Los nombres de usuario de autorización deben ser únicos",
"error.invalid-auth": "Correo electrónico o contraseña no válidos",
"error.invalid-domain": "Dominio no válido: {domain}",
"error.invalid-email": "Dirección de correo electrónico no válida",
"error.max-character-length": "La longitud máxima es {max} caracter{max, plural, one {} other {es}}",
"error.max-domains": "Demasiados dominios, el máximo es {max}",
"error.maximum": "El máximo es {max}",
"error.min-character-length": "La longitud mínima es {min} caracter{min, plural, one {} other {es}}",
"error.minimum": "El mínimo es {min}",
"error.passwords-must-match": "Las contraseñas deben coincidir",
"error.required": "Este campo es obligatorio",
"expires.on": "Expira: {date}",
"footer.github-fork": "Bifúrcame en Github",
"host.flags.block-exploits": "Bloquear Exploits Comunes",
"host.flags.cache-assets": "Cachear Recursos",
"host.flags.preserve-path": "Preservar Ruta",
"host.flags.protocols": "Protocolos",
"host.flags.websockets-upgrade": "Soporte de Websockets",
"host.forward-port": "Puerto de Reenvío",
"host.forward-scheme": "Esquema",
"hosts": "Hosts",
"http-only": "Solo HTTP",
"lets-encrypt": "Let's Encrypt",
"lets-encrypt-via-dns": "Let's Encrypt vía DNS",
"lets-encrypt-via-http": "Let's Encrypt vía HTTP",
"loading": "Cargando…",
"login.title": "Inicia sesión en tu cuenta",
"nginx-config.label": "Configuración Personalizada de Nginx",
"nginx-config.placeholder": "# ¡Introduce aquí tu configuración personalizada de Nginx bajo tu propio riesgo!",
"no-permission-error": "No tienes acceso para ver esto.",
"notfound.action": "Llévame al inicio",
"notfound.content": "Lo sentimos, pero la página que buscas no fue encontrada",
"notfound.title": "Ups… Has encontrado una página de error",
"notification.error": "Error",
"notification.object-deleted": "{object} ha sido eliminado",
"notification.object-disabled": "{object} ha sido deshabilitado",
"notification.object-enabled": "{object} ha sido habilitado",
"notification.object-renewed": "{object} ha sido renovado",
"notification.object-saved": "{object} ha sido guardado",
"notification.success": "Éxito",
"object.actions-title": "{object} #{id}",
"object.add": "Añadir {object}",
"object.delete": "Eliminar {object}",
"object.delete.content": "¿Estás seguro de que quieres eliminar este {object}?",
"object.edit": "Editar {object}",
"object.empty": "No hay {objects}",
"object.event.created": "{object} Creado",
"object.event.deleted": "{object} Eliminado",
"object.event.disabled": "{object} Deshabilitado",
"object.event.enabled": "{object} Habilitado",
"object.event.renewed": "{object} Renovado",
"object.event.updated": "{object} Actualizado",
"offline": "Desconectado",
"online": "Conectado",
"options": "Opciones",
"password": "Contraseña",
"password.generate": "Generar contraseña aleatoria",
"password.hide": "Ocultar Contraseña",
"password.show": "Mostrar Contraseña",
"permissions.hidden": "Oculto",
"permissions.manage": "Gestionar",
"permissions.view": "Solo Ver",
"permissions.visibility.all": "Todos los Elementos",
"permissions.visibility.title": "Visibilidad de Elementos",
"permissions.visibility.user": "Solo Elementos Creados",
"proxy-host": "Host Proxy",
"proxy-host.forward-host": "Nombre de Host / IP de Reenvío",
"proxy-hosts": "Hosts Proxy",
"proxy-hosts.count": "{count} {count, plural, one {Host Proxy} other {Hosts Proxy}}",
"public": "Público",
"redirection-host": "Host de Redirección",
"redirection-host.forward-domain": "Dominio de Reenvío",
"redirection-host.forward-http-code": "Código HTTP",
"redirection-hosts": "Hosts de Redirección",
"redirection-hosts.count": "{count} {count, plural, one {Host de Redirección} other {Hosts de Redirección}}",
"role.admin": "Administrador",
"role.standard-user": "Usuario Estándar",
"save": "Guardar",
"setting": "Configuración",
"settings": "Configuración",
"settings.default-site": "Sitio Predeterminado",
"settings.default-site.404": "Página 404",
"settings.default-site.444": "Sin Respuesta (444)",
"settings.default-site.congratulations": "Página de Felicitaciones",
"settings.default-site.description": "Qué mostrar cuando Nginx recibe un Host desconocido",
"settings.default-site.html": "HTML Personalizado",
"settings.default-site.html.placeholder": "<!-- Introduce aquí tu contenido HTML personalizado -->",
"settings.default-site.redirect": "Redirigir",
"setup.preamble": "Comienza creando tu cuenta de administrador.",
"setup.title": "¡Bienvenido!",
"sign-in": "Iniciar Sesión",
"ssl-certificate": "Certificado SSL",
"stream": "Stream",
"stream.forward-host": "Host de Reenvío",
"stream.incoming-port": "Puerto de Entrada",
"streams": "Streams",
"streams.count": "{count} {count, plural, one {Stream} other {Streams}}",
"streams.tcp": "TCP",
"streams.udp": "UDP",
"test": "Probar",
"user": "Usuario",
"user.change-password": "Cambiar Contraseña",
"user.confirm-password": "Confirmar Contraseña",
"user.current-password": "Contraseña Actual",
"user.edit-profile": "Editar Perfil",
"user.full-name": "Nombre Completo",
"user.login-as": "Iniciar sesión como {name}",
"user.logout": "Cerrar Sesión",
"user.new-password": "Nueva Contraseña",
"user.nickname": "Apodo",
"user.set-password": "Establecer Contraseña",
"user.set-permissions": "Establecer Permisos para {name}",
"user.switch-dark": "Cambiar a modo Oscuro",
"user.switch-light": "Cambiar a modo Claro",
"username": "Nombre de Usuario",
"users": "Usuarios"
}

View File

@@ -1,5 +0,0 @@
{
"locale-de-DE": "German",
"locale-en-US": "English",
"locale-es-ES": "Español"
}

View File

@@ -31,6 +31,6 @@ for file in *.json; do
fi fi
echo "Sorting $file" echo "Sorting $file"
jq --tab --sort-keys . "$file" | sponge "$file" tmp=$(mktemp) && jq --tab --sort-keys . "$file" > "$tmp" && mv "$tmp" "$file"
fi fi
done done

View File

@@ -0,0 +1,7 @@
## Какво представлява Списъкът за достъп?
Списъците за достъп предоставят черен или бял списък от конкретни клиентски IP адреси, както и удостоверяване за Прокси хостове чрез базова HTTP автентикация.
Можете да конфигурирате множество клиентски правила, потребителски имена и пароли в един Списък за достъп и след това да го приложите към един или повече _Прокси хостове_.
Това е най-полезно при препращани уеб услуги, които нямат вградени механизми за удостоверяване, или когато искате да защитите достъпа от неизвестни клиенти.

View File

@@ -0,0 +1,21 @@
## Помощ за сертификати
### HTTP сертификат
HTTP валидираният сертификат означава, че сървърите на Lets Encrypt ще се опитат да достигнат вашите домейни по HTTP (не по HTTPS!) и ако успеят, ще издадат сертификата.
За този метод трябва да имате създаден _Прокси хост_ за вашия/вашите домейни, който да е достъпен по HTTP и да сочи към тази Nginx инсталация. След като бъде издаден сертификат, можете да промените _Прокси хоста_ така, че да използва сертификата и за HTTPS връзки. Въпреки това, _Прокси хостът_ трябва да остане конфигуриран за достъп по HTTP, за да може сертификатът да се подновява.
Този процес _не_ поддържа wildcard домейни.
### DNS сертификат
DNS валидираният сертификат изисква използването на DNS Provider плъгин. Този DNS Provider ще бъде използван за временно създаване на записи във вашия домейн, след което Lets Encrypt ще ги провери, за да се увери, че сте собственикът, и при успех ще издаде сертификата.
Не е необходимо да имате _Прокси хост_, създаден предварително, за да заявите този тип сертификат. Нито е нужно вашият _Прокси хост_ да бъде конфигуриран за достъп по HTTP.
Този процес _поддържа_ wildcard домейни.
### Персонализиран сертификат
Използвайте тази опция, за да качите собствен SSL сертификат, предоставен от ваша сертификатна агенция.

View File

@@ -0,0 +1,10 @@
## Какво представлява 404 хост?
404 хост е просто конфигурация на хост, който показва страница с грешка 404.
Това може да е полезно, когато вашият домейн е индексиран в търсачките и искате
да предоставите по-приятна страница за грешка или да уведомите индексиращите системи,
че страниците на домейна вече не съществуват.
Допълнително предимство на този хост е възможността да проследявате логовете на заявките
към него и да виждате реферерите.

View File

@@ -0,0 +1,7 @@
## Какво представлява Прокси хост?
Прокси хост е входна точка за уеб услуга, която искате да препращате.
Той предоставя възможност за SSL терминaция на услуга, която може да няма вградена поддръжка на SSL.
Прокси хостовете са най-често използваната функция в Nginx Proxy Manager.

View File

@@ -0,0 +1,7 @@
## Какво представлява Хост за пренасочване?
Хостът за пренасочване пренасочва заявките от входящия домейн и прехвърля
потребителя към друг домейн.
Най-честата причина за използване на този тип хост е, когато вашият уебсайт
промени домейна си, но все още има линкове от търсачки или реферери, които сочат към стария домейн.

View File

@@ -0,0 +1,6 @@
## Какво представлява Потокът (Stream)?
Относително нова функция за Nginx, Потокът позволява препращане на TCP/UDP
трафик директно към друг компютър в мрежата.
Това е полезно, ако хоствате игрови сървъри, FTP или SSH сървъри.

View File

@@ -0,0 +1,6 @@
export * as AccessLists from "./AccessLists.md";
export * as Certificates from "./Certificates.md";
export * as DeadHosts from "./DeadHosts.md";
export * as ProxyHosts from "./ProxyHosts.md";
export * as RedirectionHosts from "./RedirectionHosts.md";
export * as Streams from "./Streams.md";

View File

@@ -0,0 +1,7 @@
## Cad is Liosta Rochtana ann?
Soláthraíonn Liostaí Rochtana liosta dubh nó liosta bán de sheoltaí IP cliant ar leith mar aon le fíordheimhniú do na hÓstaigh Seachfhreastalaí trí Fhíordheimhniú Bunúsach HTTP.
Is féidir leat rialacha cliant, ainmneacha úsáideora agus pasfhocail iolracha a chumrú le haghaidh Liosta Rochtana aonair agus ansin iad sin a chur i bhfeidhm ar _Óstach Seachfhreastalaí_ amháin nó níos mó.
Tá sé seo an-úsáideach i gcás seirbhísí gréasáin atreoraithe nach bhfuil meicníochtaí fíordheimhnithe ionsuite iontu nó nuair is mian leat cosaint a dhéanamh ar chliaint anaithnide.

View File

@@ -0,0 +1,21 @@
## Cabhair le Deimhnithe
### Teastas HTTP
Ciallaíonn deimhniú bailíochtaithe HTTP go ndéanfaidh freastalaithe Let's Encrypt iarracht teacht ar do fhearainn thar HTTP (ní HTTPS!) agus má éiríonn leo, eiseoidh siad do theastas.
Chun an modh seo a dhéanamh, beidh ort _Óstach Proxy_ a chruthú do do fhearainn(eanna) atá inrochtana le HTTP agus ag pointeáil chuig an suiteáil Nginx seo. Tar éis deimhniú a thabhairt, is féidir leat an _Óstach Proxy_ a mhodhnú chun an deimhniú seo a úsáid le haghaidh naisc HTTPS freisin. Mar sin féin, beidh ort an _Óstach Proxy_ a chumrú fós le haghaidh rochtain HTTP chun go ndéanfar an deimhniú a athnuachan.
_Ní thacaíonn_ an próiseas seo le fearainn fiáine.
### Teastas DNS
Éilíonn deimhniú bailíochtaithe DNS ort breiseán Soláthraí DNS a úsáid. Úsáidfear an Soláthraí DNS seo chun taifid shealadacha a chruthú ar do fhearann agus ansin déanfaidh Let's Encrypt fiosrúchán ar na taifid sin lena chinntiú gurb tusa an t-úinéir agus má éiríonn leo, eiseoidh siad do theastas.
Ní gá duit _Óstach Proxy_ a chruthú sula n-iarrann tú an cineál seo teastais. Ní gá duit do _Óstach Proxy_ a chumrú le haghaidh rochtana HTTP ach an oiread.
_Tacaíonn_ an próiseas seo le fearainn fiáine.
### Teastas Saincheaptha
Úsáid an rogha seo chun do Theastas SSL féin a uaslódáil, mar a sholáthraíonn d'Údarás Deimhnithe féin é.

View File

@@ -0,0 +1,7 @@
## Cad is Óstach 404 ann?
Is socrú óstach a thaispeánann leathanach 404 é Óstach 404.
Is féidir leis seo a bheith úsáideach nuair a bhíonn do fhearann liostaithe in innill chuardaigh agus más mian leat leathanach earráide níos deise a sholáthar nó a chur in iúl do na hinnéacsóirí cuardaigh go sonrach nach bhfuil na leathanaigh fearainn ann a thuilleadh.
Buntáiste eile a bhaineann leis an óstach seo a bheith agat ná go bhfeictear na logaí le haghaidh amas agus go bhfeictear na tagairtí.

View File

@@ -0,0 +1,7 @@
## Cad is Óstach Seachfhreastalaí ann?
Is é Óstach Seachfhreastalaí an críochphointe isteach do sheirbhís ghréasáin ar mhaith leat a atreorú.
Soláthraíonn sé foirceannadh SSL roghnach do do sheirbhís nach bhfuil tacaíocht SSL ionsuite inti b'fhéidir.
Is iad Óstaigh Seachfhreastalaí an úsáid is coitianta a bhaintear as Bainisteoir Seachfhreastalaí Nginx.

View File

@@ -0,0 +1,5 @@
## Cad is Óstach Athsheolta ann?
Déanfaidh Óstach Athsheolta iarratais a atreorú ón bhfearann ag teacht isteach agus an breathnóir a bhrú chuig fearann eile.
Is é an chúis is coitianta le húsáid a bhaint as an gcineál seo óstála ná nuair a athraíonn do shuíomh Gréasáin fearainn ach go bhfuil naisc innill chuardaigh nó atreoraithe agat fós ag tagairt don seanfhearann.

View File

@@ -0,0 +1,5 @@
## Cad is Sruth ann?
Gné réasúnta nua do Nginx is ea Sruth a sheolfaidh trácht TCP/UDP go díreach chuig ríomhaire eile ar an líonra.
Más freastalaithe cluichí, freastalaithe FTP nó SSH atá á rith agat, dfhéadfadh sé seo a bheith úsáideach.

View File

@@ -0,0 +1,6 @@
export * as AccessLists from "./AccessLists.md";
export * as Certificates from "./Certificates.md";
export * as DeadHosts from "./DeadHosts.md";
export * as ProxyHosts from "./ProxyHosts.md";
export * as RedirectionHosts from "./RedirectionHosts.md";
export * as Streams from "./Streams.md";

View File

@@ -0,0 +1,7 @@
## Apa itu Daftar Akses?
Daftar Akses menyediakan daftar hitam atau daftar putih alamat IP klien tertentu beserta autentikasi untuk Host Proxy melalui Autentikasi HTTP Basic.
Anda dapat mengonfigurasi beberapa aturan klien, nama pengguna, dan kata sandi untuk satu Daftar Akses lalu menerapkannya ke satu atau lebih _Host Proxy_.
Ini paling berguna untuk layanan web yang diteruskan yang tidak memiliki mekanisme autentikasi bawaan atau ketika Anda ingin melindungi dari klien yang tidak dikenal.

View File

@@ -0,0 +1,32 @@
## Bantuan Sertifikat
### Sertifikat HTTP
Sertifikat yang divalidasi HTTP berarti server Let's Encrypt akan
mencoba menjangkau domain Anda melalui HTTP (bukan HTTPS!) dan jika berhasil, mereka
akan menerbitkan sertifikat Anda.
Untuk metode ini, Anda harus membuat _Host Proxy_ untuk domain Anda yang
dapat diakses dengan HTTP dan mengarah ke instalasi Nginx ini. Setelah sertifikat
diberikan, Anda dapat mengubah _Host Proxy_ agar juga menggunakan sertifikat ini untuk HTTPS
koneksi. Namun, _Host Proxy_ tetap perlu dikonfigurasi untuk akses HTTP
agar sertifikat dapat diperpanjang.
Proses ini _tidak_ mendukung domain wildcard.
### Sertifikat DNS
Sertifikat yang divalidasi DNS mengharuskan Anda menggunakan plugin Penyedia DNS. Penyedia DNS ini
akan digunakan untuk membuat record sementara pada domain Anda dan kemudian Let's
Encrypt akan menanyakan record tersebut untuk memastikan Anda pemiliknya dan jika berhasil, mereka
akan menerbitkan sertifikat Anda.
Anda tidak perlu membuat _Host Proxy_ sebelum meminta jenis sertifikat ini.
Anda juga tidak perlu mengonfigurasi _Host Proxy_ untuk akses HTTP.
Proses ini _mendukung_ domain wildcard.
### Sertifikat Kustom
Gunakan opsi ini untuk mengunggah Sertifikat SSL Anda sendiri, sebagaimana disediakan oleh
Certificate Authority Anda.

View File

@@ -0,0 +1,10 @@
## Apa itu Host 404?
Host 404 adalah konfigurasi host yang menampilkan halaman 404.
Ini dapat berguna ketika domain Anda terindeks di mesin pencari dan Anda ingin
menyediakan halaman error yang lebih baik atau secara khusus memberi tahu pengindeks pencarian bahwa
halaman domain tersebut sudah tidak ada.
Manfaat lain memiliki host ini adalah melacak log untuk akses ke host tersebut dan
melihat perujuk.

View File

@@ -0,0 +1,7 @@
## Apa itu Host Proxy?
Host Proxy adalah endpoint masuk untuk layanan web yang ingin Anda teruskan.
Host ini menyediakan terminasi SSL opsional untuk layanan Anda yang mungkin tidak memiliki dukungan SSL bawaan.
Host Proxy adalah penggunaan paling umum untuk Nginx Proxy Manager.

View File

@@ -0,0 +1,5 @@
## Apa itu Host Pengalihan?
Host Pengalihan akan mengalihkan permintaan dari domain masuk dan mengarahkan pengunjung ke domain lain.
Alasan paling umum menggunakan jenis host ini adalah ketika situs Anda berpindah domain tetapi masih ada tautan mesin pencari atau perujuk yang mengarah ke domain lama.

View File

@@ -0,0 +1,6 @@
## Apa itu Stream?
Fitur yang relatif baru untuk Nginx, Stream berfungsi untuk meneruskan trafik TCP/UDP
langsung ke komputer lain di jaringan.
Jika Anda menjalankan server game, FTP, atau SSH, ini bisa sangat membantu.

View File

@@ -0,0 +1,6 @@
export * as AccessLists from "./AccessLists.md";
export * as Certificates from "./Certificates.md";
export * as DeadHosts from "./DeadHosts.md";
export * as ProxyHosts from "./ProxyHosts.md";
export * as RedirectionHosts from "./RedirectionHosts.md";
export * as Streams from "./Streams.md";

View File

@@ -1,9 +1,19 @@
// import * as de from "./de/index";
// import * as fa from "./fa/index";
import * as en from "./en/index";
import * as de from "./de/index"; import * as de from "./de/index";
import * as en from "./en/index";
import * as ga from './ga/index'
import * as id from "./id/index";
import * as it from "./it/index";
import * as ja from "./ja/index";
import * as nl from "./nl/index";
import * as pl from "./pl/index";
import * as ru from "./ru/index";
import * as sk from "./sk/index";
import * as vi from "./vi/index";
import * as zh from "./zh/index";
import * as ko from "./ko/index";
import * as bg from "./bg/index";
const items: any = { en, de }; const items: any = { en, de, ja, sk, zh, pl, ru, it, vi, nl, bg, ko, ga, id }
const fallbackLang = "en"; const fallbackLang = "en";

View File

@@ -0,0 +1,7 @@
## Che cos'è una Lista di Accesso?
La Lista di Accesso fornisce una blacklist o una whitelist di indirizzi IP specifici dei client insieme all'autenticazione per gli host proxy tramite autenticazione HTTP di base.
È possibile configurare più regole client, nomi utente e password per un singolo lista di accesso e quindi applicarlo a uno o più host proxy.
Ciò è particolarmente utile per i servizi web inoltrati che non dispongono di meccanismi di autenticazione integrati o quando si desidera proteggersi da client sconosciuti.

View File

@@ -0,0 +1,24 @@
## Guida sui Certificati
### Certificato HTTP
Un certificato convalidato HTTP significa che i server Let's Encrypttenteranno di raggiungere i tuoi domini tramite HTTP (non HTTPS!) e, in caso di esito positivo, emetteranno il tuo certificato.
Per questo metodo, dovrai creare un _Proxy Host_ per i tuoi domini chesia accessibile con HTTP e che punti a questa installazione Nginx.
Dopo che il certificato è stato rilasciato, puoi modificare il _Proxy Host_ per utilizzare questo certificato anche per le connessioni HTTPS.
Tuttavia, il _Proxy Host_ dovrà comunque essere configurato per l'accesso HTTP affinché il certificato possa essere rinnovato.
Questo processo _non_ supporta i domini wildcard.
### Certificato DNS
Un certificato convalidato dal DNS richiede l'uso di un plugin DNS Provider. Questo DNS Provider verrà utilizzato per creare record temporanei sul tuo dominio,
quindi Let's Encrypt interrogherà tali record per assicurarsi che tu sia il proprietario e, in caso di esito positivo,rilascerà il tuo certificato.
Non è necessario creare un _Proxy Host_ prima di richiedere questo tipo di certificato. Non è nemmeno necessario configurare il tuo _proxy host_ per l'accesso HTTP.
Questo processo _supporta_ i domini wildcard.
### Certificato personalizzato
Utilizza questa opzione per caricare il tuo certificato SSL, fornito dalla tua autorità di certificazione.

View File

@@ -0,0 +1,9 @@
## Che cos'è un Host 404?
Un Host 404 è semplicemente una configurazione host che mostra una pagina 404.
Questo può essere utile quando il tuo dominio è elencato nei motori di ricerca e desideri fornire una pagina di errore più gradevole o specificare agli
indicizzatori di ricerca che le pagine del dominio non esistono più.
Un altro vantaggio di avere questo host è quello di tracciare i log degli accessi e
visualizzare i referrer.

View File

@@ -0,0 +1,7 @@
## Che cos'è un Proxy Host?
Un host proxy è l'endpoint in entrata per un servizio web che si desidera inoltrare.
Fornisce la terminazione SSL opzionale per il servizio che potrebbe non avere il supporto SSL integrato.
Gli host proxy sono l'uso più comune per Nginx Proxy Manager.

View File

@@ -0,0 +1,7 @@
## Che cos'è un Host di reindirizzamento?
Un Host di reindirizzamento reindirizza le richieste provenienti dal dominio in entrata e indirizza il
visitatore verso un altro dominio.
Il motivo più comune per utilizzare questo tipo di host è quando il tuo sito web cambia
dominio, ma hai ancora link di motori di ricerca o referrer che puntano al vecchio dominio.

View File

@@ -0,0 +1,6 @@
## Che cos'è uno Stream?
Una funzionalità relativamente nuova per Nginx, uno Stream serve a inoltrare il traffico TCP/UDP
direttamente a un altro computer sulla rete.
Se gestisci server di gioco, FTP o SSH, questa funzionalità può rivelarsi molto utile.

View File

@@ -0,0 +1,6 @@
export * as AccessLists from "./AccessLists.md";
export * as Certificates from "./Certificates.md";
export * as DeadHosts from "./DeadHosts.md";
export * as ProxyHosts from "./ProxyHosts.md";
export * as RedirectionHosts from "./RedirectionHosts.md";
export * as Streams from "./Streams.md";

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