mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2026-02-05 10:22:53 +00:00
Compare commits
154 Commits
79a9653b26
...
v2.13.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3efaae320 | ||
|
|
7b3c1fd061 | ||
|
|
ee42202348 | ||
|
|
c1ad7788f1 | ||
|
|
d33bb02c74 | ||
|
|
462c134751 | ||
|
|
b7dfaddbb1 | ||
|
|
11ee4f0820 | ||
|
|
19970a4220 | ||
|
|
59bac3b468 | ||
|
|
48753fb101 | ||
|
|
2a3978ae3f | ||
|
|
4ce5da5930 | ||
|
|
89d3756ee6 | ||
|
|
58c63096e4 | ||
|
|
b01a22c393 | ||
|
|
9c25410331 | ||
|
|
b3a901bbc5 | ||
|
|
3e3396ba9a | ||
|
|
3eb493bb8b | ||
|
|
8c8221a352 | ||
|
|
582681e3ff | ||
|
|
52fae6d35f | ||
|
|
6c0ea835ce | ||
|
|
fb52655374 | ||
|
|
336726db8d | ||
|
|
4a7853163e | ||
|
|
b30f8e47e2 | ||
|
|
6fa30840be | ||
|
|
05726aaab9 | ||
|
|
f85bb79f13 | ||
|
|
471b62c7fe | ||
|
|
55a1e0a4e7 | ||
|
|
f25afa3590 | ||
|
|
9211ba6d1a | ||
|
|
aeb44244a7 | ||
|
|
d2d204ab8e | ||
|
|
427afa55b4 | ||
|
|
bbe98a639a | ||
|
|
f0c0b465d9 | ||
|
|
6c2f6a9d39 | ||
|
|
2f6e3ad804 | ||
|
|
5e6ead1eee | ||
|
|
da519e72ba | ||
|
|
b13ebb2247 | ||
|
|
6b322582b9 | ||
|
|
7fe5070337 | ||
|
|
1b8f1fbb79 | ||
|
|
4abea1247d | ||
|
|
073ee95e56 | ||
|
|
fec8b3b083 | ||
|
|
168078eb40 | ||
|
|
2c9f8f4d64 | ||
|
|
8403a0c761 | ||
|
|
d18c8cf4f1 | ||
|
|
bf4eab541a | ||
|
|
f9edcb10e6 | ||
|
|
ba43c144f6 | ||
|
|
896951f6cd | ||
|
|
865b566ea6 | ||
|
|
45bc44c6fa | ||
|
|
4ff402fff4 | ||
|
|
1c6f54fa3c | ||
|
|
e8ca72fb6a | ||
|
|
4712633568 | ||
|
|
a1fb54c394 | ||
|
|
927e57257b | ||
|
|
e353a66556 | ||
|
|
991bddf891 | ||
|
|
c076ad145c | ||
|
|
80cf4406d5 | ||
|
|
3cb124d5a0 | ||
|
|
03b0513a24 | ||
|
|
0528d65317 | ||
|
|
f9991084fc | ||
|
|
56875bba52 | ||
|
|
b55f51bd63 | ||
|
|
20e2d5ffb3 | ||
|
|
86b7394620 | ||
|
|
91a1f39c02 | ||
|
|
5c114e9db7 | ||
|
|
fec9bffe29 | ||
|
|
e3cdc8bb30 | ||
|
|
ba79eefe5e | ||
|
|
bb94ce75c1 | ||
|
|
847c58b170 | ||
|
|
89b8b747e1 | ||
|
|
3231023513 | ||
|
|
dc89635971 | ||
|
|
cfa98361d1 | ||
|
|
c2177abe39 | ||
|
|
2c6d614597 | ||
|
|
484ce8db3c | ||
|
|
2c11c0c7e2 | ||
|
|
f1039ce2ef | ||
|
|
d49ff6e7c2 | ||
|
|
a87f24c9dc | ||
|
|
decdfec447 | ||
|
|
32ab3faf57 | ||
|
|
c7f999fa7a | ||
|
|
de7d3b0d19 | ||
|
|
2d4b7399c0 | ||
|
|
316b758455 | ||
|
|
890d06c863 | ||
|
|
81f2aa17d4 | ||
|
|
9b4c34915c | ||
|
|
fce569ca21 | ||
|
|
87ec9c4bdf | ||
|
|
2650648d68 | ||
|
|
fdc0c29f28 | ||
|
|
6cae088432 | ||
|
|
9d8c4cc30b | ||
|
|
66ebecdb43 | ||
|
|
60f3ee03c0 | ||
|
|
a4d54a0291 | ||
|
|
7536b1b1c9 | ||
|
|
5288fbd7af | ||
|
|
2c630bbdca | ||
|
|
0ec1a09c30 | ||
|
|
118c4793e3 | ||
|
|
d7384c568f | ||
|
|
0bcfe0bba6 | ||
|
|
74cbfb2c58 | ||
|
|
8ef65caa5a | ||
|
|
bc341c1dff | ||
|
|
5fc9febf1f | ||
|
|
b23ceebfd8 | ||
|
|
c281fc54a1 | ||
|
|
d0f7dc5b48 | ||
|
|
fb53df862e | ||
|
|
8d8463ae41 | ||
|
|
8774cfe5f9 | ||
|
|
4ca5cadd19 | ||
|
|
45a8d50e03 | ||
|
|
960d4bfe6f | ||
|
|
8c3c964c52 | ||
|
|
afd6134a3e | ||
|
|
9b2d60e67b | ||
|
|
9807e25d45 | ||
|
|
824c895f52 | ||
|
|
7f9b9dfea4 | ||
|
|
d848ba9f65 | ||
|
|
47db5c9aa6 | ||
|
|
1c442dcce6 | ||
|
|
dadd10f89b | ||
|
|
75c012b558 | ||
|
|
9be1381ffe | ||
|
|
f40fe56572 | ||
|
|
911476f82f | ||
|
|
e86a34f2f3 | ||
|
|
f02145c5ef | ||
|
|
66fa08fd8e | ||
|
|
d783cc3b90 | ||
|
|
17cc75fe7d |
@@ -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.4-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>
|
||||||
|
|||||||
@@ -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
288
backend/internal/2fa.js
Normal 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;
|
||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
18
backend/schema/components/token-challenge.json
Normal file
18
backend/schema/components/token-challenge.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
55
backend/schema/paths/tokens/2fa/post.json
Normal file
55
backend/schema/paths/tokens/2fa/post.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,7 +50,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"schema": {
|
"schema": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
"$ref": "../../components/token-object.json"
|
"$ref": "../../components/token-object.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "../../components/token-challenge.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
92
backend/schema/paths/users/userID/2fa/backup-codes/post.json
Normal file
92
backend/schema/paths/users/userID/2fa/backup-codes/post.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
48
backend/schema/paths/users/userID/2fa/delete.json
Normal file
48
backend/schema/paths/users/userID/2fa/delete.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
92
backend/schema/paths/users/userID/2fa/enable/post.json
Normal file
92
backend/schema/paths/users/userID/2fa/enable/post.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
57
backend/schema/paths/users/userID/2fa/get.json
Normal file
57
backend/schema/paths/users/userID/2fa/get.json
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
backend/schema/paths/users/userID/2fa/post.json
Normal file
52
backend/schema/paths/users/userID/2fa/post.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -293,6 +293,11 @@
|
|||||||
"$ref": "./paths/tokens/post.json"
|
"$ref": "./paths/tokens/post.json"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/tokens/2fa": {
|
||||||
|
"post": {
|
||||||
|
"$ref": "./paths/tokens/2fa/post.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
"/version/check": {
|
"/version/check": {
|
||||||
"get": {
|
"get": {
|
||||||
"$ref": "./paths/version/check/get.json"
|
"$ref": "./paths/version/check/get.json"
|
||||||
@@ -317,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"
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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==
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
3
docker/rootfs/etc/nginx/conf.d/include/log-stream.conf
Normal file
3
docker/rootfs/etc/nginx/conf.d/include/log-stream.conf
Normal 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;
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
946
docs/yarn.lock
946
docs/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -8,12 +8,19 @@
|
|||||||
|
|
||||||
const allLocales = [
|
const allLocales = [
|
||||||
["en", "en-US"],
|
["en", "en-US"],
|
||||||
["es", "es-ES"],
|
|
||||||
["de", "de-DE"],
|
["de", "de-DE"],
|
||||||
|
["es", "es-ES"],
|
||||||
|
["it", "it-IT"],
|
||||||
|
["ja", "ja-JP"],
|
||||||
|
["nl", "nl-NL"],
|
||||||
|
["pl", "pl-PL"],
|
||||||
["ru", "ru-RU"],
|
["ru", "ru-RU"],
|
||||||
["sk", "sk-SK"],
|
["sk", "sk-SK"],
|
||||||
|
["vi", "vi-VN"],
|
||||||
["zh", "zh-CN"],
|
["zh", "zh-CN"],
|
||||||
["pl", "pl-PL"],
|
["ko", "ko-KR"],
|
||||||
|
["bg", "bg-BG"],
|
||||||
|
["id", "id-ID"],
|
||||||
];
|
];
|
||||||
|
|
||||||
const ignoreUnused = [
|
const ignoreUnused = [
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 });
|
||||||
|
|||||||
@@ -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 },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -60,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";
|
||||||
|
|||||||
@@ -25,3 +25,22 @@ export interface VersionCheckResponse {
|
|||||||
latest: string | null;
|
latest: string | null;
|
||||||
updateAvailable: boolean;
|
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[];
|
||||||
|
}
|
||||||
|
|||||||
37
frontend/src/api/backend/twoFactor.ts
Normal file
37
frontend/src/api/backend/twoFactor.ts
Normal 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 },
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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" />,
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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" />,
|
||||||
})) || [];
|
})) || [];
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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="?"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 }} />
|
||||||
— <span className="badge">{getEventValue(row)}</span>
|
— <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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,75 +2,67 @@ import { createIntl, createIntlCache } from "react-intl";
|
|||||||
import langDe from "./lang/de.json";
|
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 langGa from "./lang/ga.json";
|
||||||
|
import langIt from "./lang/it.json";
|
||||||
import langJa from "./lang/ja.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 langRu from "./lang/ru.json";
|
||||||
import langSk from "./lang/sk.json";
|
import langSk from "./lang/sk.json";
|
||||||
|
import langVi from "./lang/vi.json";
|
||||||
import langZh from "./lang/zh.json";
|
import langZh from "./lang/zh.json";
|
||||||
import langPl from "./lang/pl.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],
|
||||||
["de", "de-DE"],
|
["de", "de-DE", langDe],
|
||||||
["es", "es-ES"],
|
["es", "es-ES", langEs],
|
||||||
["ja", "ja-JP"],
|
["ga", "ga-IE", langGa],
|
||||||
["ru", "ru-RU"],
|
["ja", "ja-JP", langJa],
|
||||||
["sk", "sk-SK"],
|
["it", "it-IT", langIt],
|
||||||
["zh", "zh-CN"],
|
["nl", "nl-NL", langNl],
|
||||||
["pl", "pl-PL"],
|
["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 "de":
|
// ensure this lang exists in localeOptions above, otherwise fallback to en
|
||||||
return Object.assign({}, langList, langEn, langDe);
|
if (thisLocale === "en" || !localeOptions.some(([code]) => code === thisLocale)) {
|
||||||
case "es":
|
|
||||||
return Object.assign({}, langList, langEn, langEs);
|
|
||||||
case "ja":
|
|
||||||
return Object.assign({}, langList, langEn, langJa);
|
|
||||||
case "ru":
|
|
||||||
return Object.assign({}, langList, langEn, langRu);
|
|
||||||
case "sk":
|
|
||||||
return Object.assign({}, langList, langEn, langSk);
|
|
||||||
case "zh":
|
|
||||||
return Object.assign({}, langList, langEn, langZh);
|
|
||||||
case "pl":
|
|
||||||
return Object.assign({}, langList, langEn, langPl);
|
|
||||||
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
|
||||||
case "ja-JP":
|
ko: "kr", // Korea
|
||||||
case "ja":
|
};
|
||||||
return "JP";
|
|
||||||
case "ru-RU":
|
if (specialCases[thisLocale]) {
|
||||||
case "ru":
|
return specialCases[thisLocale].toUpperCase();
|
||||||
return "RU";
|
|
||||||
case "sk-SK":
|
|
||||||
case "sk":
|
|
||||||
return "SK";
|
|
||||||
case "zh":
|
|
||||||
case "zh-CN":
|
|
||||||
return "CN";
|
|
||||||
case "pl":
|
|
||||||
case "pl-PL":
|
|
||||||
return "PL";
|
|
||||||
default:
|
|
||||||
return "EN";
|
|
||||||
}
|
}
|
||||||
|
return thisLocale.toUpperCase();
|
||||||
};
|
};
|
||||||
|
|
||||||
const getLocale = (short = false) => {
|
const getLocale = (short = false) => {
|
||||||
@@ -131,4 +123,6 @@ const T = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log("L:", localeOptions);
|
||||||
|
|
||||||
export { localeOptions, getFlagCodeForLocale, getLocale, createIntl, changeLocale, intl, T };
|
export { localeOptions, getFlagCodeForLocale, getLocale, createIntl, changeLocale, intl, T };
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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", () => {
|
||||||
|
|||||||
@@ -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}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
7
frontend/src/locale/src/HelpDoc/bg/AccessLists.md
Normal file
7
frontend/src/locale/src/HelpDoc/bg/AccessLists.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
## Какво представлява Списъкът за достъп?
|
||||||
|
|
||||||
|
Списъците за достъп предоставят черен или бял списък от конкретни клиентски IP адреси, както и удостоверяване за Прокси хостове чрез базова HTTP автентикация.
|
||||||
|
|
||||||
|
Можете да конфигурирате множество клиентски правила, потребителски имена и пароли в един Списък за достъп и след това да го приложите към един или повече _Прокси хостове_.
|
||||||
|
|
||||||
|
Това е най-полезно при препращани уеб услуги, които нямат вградени механизми за удостоверяване, или когато искате да защитите достъпа от неизвестни клиенти.
|
||||||
21
frontend/src/locale/src/HelpDoc/bg/Certificates.md
Normal file
21
frontend/src/locale/src/HelpDoc/bg/Certificates.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
## Помощ за сертификати
|
||||||
|
|
||||||
|
### HTTP сертификат
|
||||||
|
|
||||||
|
HTTP валидираният сертификат означава, че сървърите на Let’s Encrypt ще се опитат да достигнат вашите домейни по HTTP (не по HTTPS!) и ако успеят, ще издадат сертификата.
|
||||||
|
|
||||||
|
За този метод трябва да имате създаден _Прокси хост_ за вашия/вашите домейни, който да е достъпен по HTTP и да сочи към тази Nginx инсталация. След като бъде издаден сертификат, можете да промените _Прокси хоста_ така, че да използва сертификата и за HTTPS връзки. Въпреки това, _Прокси хостът_ трябва да остане конфигуриран за достъп по HTTP, за да може сертификатът да се подновява.
|
||||||
|
|
||||||
|
Този процес _не_ поддържа wildcard домейни.
|
||||||
|
|
||||||
|
### DNS сертификат
|
||||||
|
|
||||||
|
DNS валидираният сертификат изисква използването на DNS Provider плъгин. Този DNS Provider ще бъде използван за временно създаване на записи във вашия домейн, след което Let’s Encrypt ще ги провери, за да се увери, че сте собственикът, и при успех ще издаде сертификата.
|
||||||
|
|
||||||
|
Не е необходимо да имате _Прокси хост_, създаден предварително, за да заявите този тип сертификат. Нито е нужно вашият _Прокси хост_ да бъде конфигуриран за достъп по HTTP.
|
||||||
|
|
||||||
|
Този процес _поддържа_ wildcard домейни.
|
||||||
|
|
||||||
|
### Персонализиран сертификат
|
||||||
|
|
||||||
|
Използвайте тази опция, за да качите собствен SSL сертификат, предоставен от ваша сертификатна агенция.
|
||||||
10
frontend/src/locale/src/HelpDoc/bg/DeadHosts.md
Normal file
10
frontend/src/locale/src/HelpDoc/bg/DeadHosts.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
## Какво представлява 404 хост?
|
||||||
|
|
||||||
|
404 хост е просто конфигурация на хост, който показва страница с грешка 404.
|
||||||
|
|
||||||
|
Това може да е полезно, когато вашият домейн е индексиран в търсачките и искате
|
||||||
|
да предоставите по-приятна страница за грешка или да уведомите индексиращите системи,
|
||||||
|
че страниците на домейна вече не съществуват.
|
||||||
|
|
||||||
|
Допълнително предимство на този хост е възможността да проследявате логовете на заявките
|
||||||
|
към него и да виждате реферерите.
|
||||||
7
frontend/src/locale/src/HelpDoc/bg/ProxyHosts.md
Normal file
7
frontend/src/locale/src/HelpDoc/bg/ProxyHosts.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
## Какво представлява Прокси хост?
|
||||||
|
|
||||||
|
Прокси хост е входна точка за уеб услуга, която искате да препращате.
|
||||||
|
|
||||||
|
Той предоставя възможност за SSL терминaция на услуга, която може да няма вградена поддръжка на SSL.
|
||||||
|
|
||||||
|
Прокси хостовете са най-често използваната функция в Nginx Proxy Manager.
|
||||||
7
frontend/src/locale/src/HelpDoc/bg/RedirectionHosts.md
Normal file
7
frontend/src/locale/src/HelpDoc/bg/RedirectionHosts.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
## Какво представлява Хост за пренасочване?
|
||||||
|
|
||||||
|
Хостът за пренасочване пренасочва заявките от входящия домейн и прехвърля
|
||||||
|
потребителя към друг домейн.
|
||||||
|
|
||||||
|
Най-честата причина за използване на този тип хост е, когато вашият уебсайт
|
||||||
|
промени домейна си, но все още има линкове от търсачки или реферери, които сочат към стария домейн.
|
||||||
6
frontend/src/locale/src/HelpDoc/bg/Streams.md
Normal file
6
frontend/src/locale/src/HelpDoc/bg/Streams.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
## Какво представлява Потокът (Stream)?
|
||||||
|
|
||||||
|
Относително нова функция за Nginx, Потокът позволява препращане на TCP/UDP
|
||||||
|
трафик директно към друг компютър в мрежата.
|
||||||
|
|
||||||
|
Това е полезно, ако хоствате игрови сървъри, FTP или SSH сървъри.
|
||||||
6
frontend/src/locale/src/HelpDoc/bg/index.ts
Normal file
6
frontend/src/locale/src/HelpDoc/bg/index.ts
Normal 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";
|
||||||
7
frontend/src/locale/src/HelpDoc/ga/AccessLists.md
Normal file
7
frontend/src/locale/src/HelpDoc/ga/AccessLists.md
Normal 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.
|
||||||
21
frontend/src/locale/src/HelpDoc/ga/Certificates.md
Normal file
21
frontend/src/locale/src/HelpDoc/ga/Certificates.md
Normal 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 é.
|
||||||
7
frontend/src/locale/src/HelpDoc/ga/DeadHosts.md
Normal file
7
frontend/src/locale/src/HelpDoc/ga/DeadHosts.md
Normal 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í.
|
||||||
7
frontend/src/locale/src/HelpDoc/ga/ProxyHosts.md
Normal file
7
frontend/src/locale/src/HelpDoc/ga/ProxyHosts.md
Normal 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.
|
||||||
5
frontend/src/locale/src/HelpDoc/ga/RedirectionHosts.md
Normal file
5
frontend/src/locale/src/HelpDoc/ga/RedirectionHosts.md
Normal 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.
|
||||||
5
frontend/src/locale/src/HelpDoc/ga/Streams.md
Normal file
5
frontend/src/locale/src/HelpDoc/ga/Streams.md
Normal 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, d’fhéadfadh sé seo a bheith úsáideach.
|
||||||
6
frontend/src/locale/src/HelpDoc/ga/index.ts
Normal file
6
frontend/src/locale/src/HelpDoc/ga/index.ts
Normal 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";
|
||||||
7
frontend/src/locale/src/HelpDoc/id/AccessLists.md
Normal file
7
frontend/src/locale/src/HelpDoc/id/AccessLists.md
Normal 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.
|
||||||
32
frontend/src/locale/src/HelpDoc/id/Certificates.md
Normal file
32
frontend/src/locale/src/HelpDoc/id/Certificates.md
Normal 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.
|
||||||
10
frontend/src/locale/src/HelpDoc/id/DeadHosts.md
Normal file
10
frontend/src/locale/src/HelpDoc/id/DeadHosts.md
Normal 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.
|
||||||
7
frontend/src/locale/src/HelpDoc/id/ProxyHosts.md
Normal file
7
frontend/src/locale/src/HelpDoc/id/ProxyHosts.md
Normal 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.
|
||||||
5
frontend/src/locale/src/HelpDoc/id/RedirectionHosts.md
Normal file
5
frontend/src/locale/src/HelpDoc/id/RedirectionHosts.md
Normal 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.
|
||||||
6
frontend/src/locale/src/HelpDoc/id/Streams.md
Normal file
6
frontend/src/locale/src/HelpDoc/id/Streams.md
Normal 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.
|
||||||
6
frontend/src/locale/src/HelpDoc/id/index.ts
Normal file
6
frontend/src/locale/src/HelpDoc/id/index.ts
Normal 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";
|
||||||
@@ -1,20 +1,34 @@
|
|||||||
import * as de from "./de/index";
|
import * as de from "./de/index";
|
||||||
import * as en from "./en/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 ja from "./ja/index";
|
||||||
import * as sk from "./sk/index";
|
import * as nl from "./nl/index";
|
||||||
import * as zh from "./zh/index";
|
|
||||||
import * as pl from "./pl/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, ja, sk, zh, pl };
|
const items: any = { en, de, ja, sk, zh, pl, ru, it, vi, nl, bg, ko, ga, id }
|
||||||
|
|
||||||
const fallbackLang = "en";
|
const fallbackLang = "en";
|
||||||
|
|
||||||
export const getHelpFile = (lang: string, section: string): string => {
|
export const getHelpFile = (lang: string, section: string): string => {
|
||||||
if (typeof items[lang] !== "undefined" && typeof items[lang][section] !== "undefined") {
|
if (
|
||||||
|
typeof items[lang] !== "undefined" &&
|
||||||
|
typeof items[lang][section] !== "undefined"
|
||||||
|
) {
|
||||||
return items[lang][section].default;
|
return items[lang][section].default;
|
||||||
}
|
}
|
||||||
// Fallback to English
|
// Fallback to English
|
||||||
if (typeof items[fallbackLang] !== "undefined" && typeof items[fallbackLang][section] !== "undefined") {
|
if (
|
||||||
|
typeof items[fallbackLang] !== "undefined" &&
|
||||||
|
typeof items[fallbackLang][section] !== "undefined"
|
||||||
|
) {
|
||||||
return items[fallbackLang][section].default;
|
return items[fallbackLang][section].default;
|
||||||
}
|
}
|
||||||
throw new Error(`Cannot load help doc for ${lang}-${section}`);
|
throw new Error(`Cannot load help doc for ${lang}-${section}`);
|
||||||
|
|||||||
7
frontend/src/locale/src/HelpDoc/it/AccessLists.md
Normal file
7
frontend/src/locale/src/HelpDoc/it/AccessLists.md
Normal 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.
|
||||||
24
frontend/src/locale/src/HelpDoc/it/Certificates.md
Normal file
24
frontend/src/locale/src/HelpDoc/it/Certificates.md
Normal 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.
|
||||||
9
frontend/src/locale/src/HelpDoc/it/DeadHosts.md
Normal file
9
frontend/src/locale/src/HelpDoc/it/DeadHosts.md
Normal 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.
|
||||||
7
frontend/src/locale/src/HelpDoc/it/ProxyHosts.md
Normal file
7
frontend/src/locale/src/HelpDoc/it/ProxyHosts.md
Normal 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.
|
||||||
7
frontend/src/locale/src/HelpDoc/it/RedirectionHosts.md
Normal file
7
frontend/src/locale/src/HelpDoc/it/RedirectionHosts.md
Normal 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.
|
||||||
6
frontend/src/locale/src/HelpDoc/it/Streams.md
Normal file
6
frontend/src/locale/src/HelpDoc/it/Streams.md
Normal 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.
|
||||||
6
frontend/src/locale/src/HelpDoc/it/index.ts
Normal file
6
frontend/src/locale/src/HelpDoc/it/index.ts
Normal 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";
|
||||||
11
frontend/src/locale/src/HelpDoc/ko/AccessLists.md
Normal file
11
frontend/src/locale/src/HelpDoc/ko/AccessLists.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
## 접근 정책이란?
|
||||||
|
|
||||||
|
접근 정책은 특정 클라이언트 IP 주소를 허용하거나 거부할 수 있으며,
|
||||||
|
프록시 호스트에 기본 HTTP 인증(Basic Auth) 을 적용할 수 있는 기능입니다.
|
||||||
|
|
||||||
|
하나의 접근 목록에 여러 클라이언트 규칙과 사용자 이름, 비밀번호를 추가한 뒤
|
||||||
|
이를 하나 이상의 프록시 호스트에 적용할 수 있습니다.
|
||||||
|
|
||||||
|
이 기능은 인증 기능이 없는 웹 서비스에 인증을 추가하거나,
|
||||||
|
알 수 없는 클라이언트로부터 서비스를 보호할 때 유용합니다.
|
||||||
|
|
||||||
28
frontend/src/locale/src/HelpDoc/ko/Certificates.md
Normal file
28
frontend/src/locale/src/HelpDoc/ko/Certificates.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
## 인증서 도움말
|
||||||
|
|
||||||
|
### HTTP 인증서
|
||||||
|
|
||||||
|
HTTP 검증 방식의 인증서는 Let's Encrypt 서버가 **HTTPS가 아닌 HTTP로** 해당 도메인에 접속을 시도해 응답이 확인되면 인증서를 발급하는 방식입니다.
|
||||||
|
|
||||||
|
이 방식을 사용하려면 도메인에 대한 **프록시 호스트가 미리 생성되어 있어야 하며**, HTTP로 접근할 수 있어야 하고 Nginx Proxy Manager가 설치된 서버를 가리켜야 합니다. 인증서가 발급된 이후에는 해당 프록시 호스트에 HTTPS용 인증서를 적용할 수 있습니다.
|
||||||
|
|
||||||
|
다만, **인증서 자동 갱신을 위해서는 HTTP 접근이 계속 필요합니다.**
|
||||||
|
|
||||||
|
이 방식은 **와일드카드 도메인을 지원하지 않습니다.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### DNS 인증서
|
||||||
|
|
||||||
|
DNS 검증 방식의 인증서는 DNS 공급자 플러그인을 사용해야 합니다. 이 플러그인은 도메인에 임시 DNS 레코드를 생성하며, Let's Encrypt는 해당 레코드를 조회해 도메인 소유 여부를 확인합니다. 검증이 성공하면 인증서가 발급됩니다.
|
||||||
|
|
||||||
|
이 방식은 인증서를 요청하기 전에 **프록시 호스트를 생성할 필요가 없으며**, 프록시 호스트에 HTTP 접근을 설정할 필요도 없습니다.
|
||||||
|
|
||||||
|
이 방식은 **와일드카드 도메인을 지원합니다.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 사용자 지정 인증서
|
||||||
|
|
||||||
|
이 옵션을 사용하면 직접 보유한 인증 기관(CA)에서 발급한 SSL 인증서를 직접 업로드하여 사용할 수 있습니다.
|
||||||
|
|
||||||
10
frontend/src/locale/src/HelpDoc/ko/DeadHosts.md
Normal file
10
frontend/src/locale/src/HelpDoc/ko/DeadHosts.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
## 404 호스트란?
|
||||||
|
|
||||||
|
404 호스트는 404 오류 페이지를 표시하도록 구성된 호스트입니다.
|
||||||
|
|
||||||
|
이 기능은 도메인이 검색 엔진에 이미 색인되어 있을 때,
|
||||||
|
더 깔끔한 오류 페이지를 제공하거나 해당 페이지가 더 이상 존재하지 않음을
|
||||||
|
검색 엔진에게 명확하게 알려야 할 때 유용합니다.
|
||||||
|
|
||||||
|
또한 404 호스트를 사용하면 접근 로그를 확인하고, 어떤 경로(Referrer)를 통해 들어왔는지 추적할 수 있다는 장점도 있습니다.
|
||||||
|
|
||||||
8
frontend/src/locale/src/HelpDoc/ko/ProxyHosts.md
Normal file
8
frontend/src/locale/src/HelpDoc/ko/ProxyHosts.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
## 프록시 호스트란?
|
||||||
|
|
||||||
|
프록시 호스트는 외부에서 들어오는 웹 요청을 받아 지정한 전달 대상으로 전달하는 역할을 합니다.
|
||||||
|
|
||||||
|
원래 SSL을 지원하지 않는 대상이라도, 프록시 호스트를 통해 SSL(HTTPS) 연결을 적용할 수 있습니다.
|
||||||
|
|
||||||
|
프록시 호스트는 Nginx Proxy Manager에서 가장 일반적으로 사용되는 기능입니다.
|
||||||
|
|
||||||
7
frontend/src/locale/src/HelpDoc/ko/RedirectionHosts.md
Normal file
7
frontend/src/locale/src/HelpDoc/ko/RedirectionHosts.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
## 리다이렉션 호스트란?
|
||||||
|
|
||||||
|
리다이렉션 호스트는 외부에서 들어오는 도메인 요청을 다른 도메인으로 자동 이동(리다이렉트)시키는 역할을 합니다.
|
||||||
|
|
||||||
|
이 유형의 호스트는 주로 웹사이트의 도메인이 변경되었지만,
|
||||||
|
검색 엔진이나 다른 사이트에 이전 도메인 링크가 남아 있을 때 사용하면 가장 효과적입니다.
|
||||||
|
|
||||||
7
frontend/src/locale/src/HelpDoc/ko/Streams.md
Normal file
7
frontend/src/locale/src/HelpDoc/ko/Streams.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
## 호스트 스트림이란?
|
||||||
|
|
||||||
|
호스트 스트림은 비교적 최근에 Nginx에 추가된 기능으로,
|
||||||
|
TCP/UDP 트래픽을 네트워크 내의 다른 컴퓨터로 직접 전달하는 데 사용됩니다.
|
||||||
|
|
||||||
|
게임 서버나 FTP, SSH 서버 등을 운영할 때 유용하게 사용할 수 있습니다.
|
||||||
|
|
||||||
7
frontend/src/locale/src/HelpDoc/ko/index.ts
Normal file
7
frontend/src/locale/src/HelpDoc/ko/index.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
7
frontend/src/locale/src/HelpDoc/nl/AccessLists.md
Normal file
7
frontend/src/locale/src/HelpDoc/nl/AccessLists.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
## Wat is een Toegangslijst?
|
||||||
|
|
||||||
|
Toeganslijsten bieden een zwarte- of witte lijst van specifieke client IP-adressen samen met authenticatie voor de Proxy Hosts via Basic HTTP Authenticatie.
|
||||||
|
|
||||||
|
Je kan meerdere client regels, gebruikersnamen en wachtwoorden voor een enkele Toegangslijst configureren en toepassen op één of meerdere _Proxy Hosts_.
|
||||||
|
|
||||||
|
Dit is het meest nuttig voor doorgestuurde webdiensten die geen authenticatiemechanismen hebben of wanneer je wilt beveiligen tegen onbekende bezoekers.
|
||||||
31
frontend/src/locale/src/HelpDoc/nl/Certificates.md
Normal file
31
frontend/src/locale/src/HelpDoc/nl/Certificates.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
## Certificaten Hulp
|
||||||
|
|
||||||
|
### HTTP Certificaat
|
||||||
|
|
||||||
|
Een HTTP gevalideerd certificaat betekent dat Let's Encrypt servers
|
||||||
|
zullen proberen om over HTTP te bereiken (niet HTTPS!) en als dat gelukt is, zal
|
||||||
|
jouw certificaat worden uitgegeven.
|
||||||
|
|
||||||
|
Voor deze zal je een _Proxy Host_ moeten hebben die is toegankelijk via HTTP en
|
||||||
|
die naar deze Nginx installatie wijst. Nadat een certificaat is uitgegeven kan je
|
||||||
|
de _Proxy Host_ wijzigen om ook HTTPS toegang te geven. Maar de _Proxy Host_ zal
|
||||||
|
nog moeten worden geconfigureerd voor HTTP toegang om het certificaat te verlengen.
|
||||||
|
|
||||||
|
Dit proces ondersteunt geen domeinen met wildcards.
|
||||||
|
|
||||||
|
### DNS Certificaat
|
||||||
|
|
||||||
|
Een DNS gevalideerd certificaat zal gebruik maken van een DNS Provider plugin. De
|
||||||
|
DNS Provider zal tijdelijke records op jouw domein maken en Let's Encrypt zal deze
|
||||||
|
records opvragen om te controleren of je de eigenaar bent. Als dat is gecontroleerd
|
||||||
|
is zal Let's Encrypt het certificaat uitgeven.
|
||||||
|
|
||||||
|
Je hebt geen _Proxy Host_ nodig om dit soort certificaat aan te vragen. Je hebt dus
|
||||||
|
geen HTTP _Proxy Host_ nodig.
|
||||||
|
|
||||||
|
Dit proces ondersteunt _wel_ domeinen met wildcards.
|
||||||
|
|
||||||
|
### Aangepast Certificaat
|
||||||
|
|
||||||
|
Gebruik deze optie om jouw eigen SSL Certificaat te uploaden, zoals
|
||||||
|
geleverd door jouw eigen Certificate Authority.
|
||||||
10
frontend/src/locale/src/HelpDoc/nl/DeadHosts.md
Normal file
10
frontend/src/locale/src/HelpDoc/nl/DeadHosts.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
## Wat is een 404 Host?
|
||||||
|
|
||||||
|
Simpel gezegd is een 404 Host een host setup die een 404 pagina weergeeft.
|
||||||
|
|
||||||
|
Dit kan nuttig zijn wanneer jouw domein is opgegeven in zoekmachines en je wil
|
||||||
|
een betere foutpagina leveren of specifiek om te zeggen tegen de zoekmachines dat
|
||||||
|
het domein niet langer bestaat.
|
||||||
|
|
||||||
|
Een ander voordeel van het hebben van een 404 Host is om de logs voor bezoeken
|
||||||
|
te volgen en de referenties te bekijken.
|
||||||
7
frontend/src/locale/src/HelpDoc/nl/ProxyHosts.md
Normal file
7
frontend/src/locale/src/HelpDoc/nl/ProxyHosts.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
## Wat is een Proxy Host?
|
||||||
|
|
||||||
|
Een Proxy Host is de inkomende endpoint voor een webdienst dat je wilt doorsturen.
|
||||||
|
|
||||||
|
Het biedt optionele SSL voor je dienst die mogelijk geen SSL ondersteuning heeft.
|
||||||
|
|
||||||
|
Proxy Hosts worden het meest gebruikt in Nginx Proxy Manager.
|
||||||
7
frontend/src/locale/src/HelpDoc/nl/RedirectionHosts.md
Normal file
7
frontend/src/locale/src/HelpDoc/nl/RedirectionHosts.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
## Wat is een Redirection Host?
|
||||||
|
|
||||||
|
Een Redirection Host zal verzoeken van de inkomende domeinnaam doorsturen, en de bezoeker
|
||||||
|
omleiden naar een andere domeinnaam.
|
||||||
|
|
||||||
|
Het gebruik van een Redirection Host is vooral handig wanneer je jouw website verandert
|
||||||
|
maar je nog zoekmachines of referenties naar de oude domeinnaam hebben.
|
||||||
6
frontend/src/locale/src/HelpDoc/nl/Streams.md
Normal file
6
frontend/src/locale/src/HelpDoc/nl/Streams.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
## Wat is een Stream?
|
||||||
|
|
||||||
|
Streams zijn een nieuwe toevoeging aan Nginx, die toelaat om TCP/UDP
|
||||||
|
verkeer naar een ander computer op het netwerk te sturen.
|
||||||
|
|
||||||
|
Als je game servers, FTP of SSH servers draait kan dit handig zijn.
|
||||||
6
frontend/src/locale/src/HelpDoc/nl/index.ts
Normal file
6
frontend/src/locale/src/HelpDoc/nl/index.ts
Normal 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";
|
||||||
7
frontend/src/locale/src/HelpDoc/ru/AccessLists.md
Normal file
7
frontend/src/locale/src/HelpDoc/ru/AccessLists.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
## Что такое список доступа?
|
||||||
|
|
||||||
|
Списки доступа позволяют задавать белый/чёрный список IP‑адресов клиентов и настраивать аутентификацию для прокси‑хостов через базовую HTTP‑аутентификацию.
|
||||||
|
|
||||||
|
Для одного списка доступа можно настроить несколько правил клиентов, логины и пароли, а затем применить его к одному или нескольким _прокси‑хостам_.
|
||||||
|
|
||||||
|
Это особенно полезно для проксируемых веб‑сервисов без встроенной аутентификации или когда нужно защититься от неизвестных клиентов.
|
||||||
21
frontend/src/locale/src/HelpDoc/ru/Certificates.md
Normal file
21
frontend/src/locale/src/HelpDoc/ru/Certificates.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
## Справка по сертификатам
|
||||||
|
|
||||||
|
### HTTP-сертификат
|
||||||
|
|
||||||
|
Сертификат, подтверждённый по HTTP, означает, что серверы Let's Encrypt попытаются обратиться к вашим доменам по HTTP (не HTTPS!) и при успехе выпустят сертификат.
|
||||||
|
|
||||||
|
Для этого метода должен существовать _прокси‑хост_ для ваших доменов, доступный по HTTP и указывающий на эту установку Nginx. После выдачи сертификата вы можете настроить _прокси‑хост_ на использование этого сертификата для HTTPS‑подключений. Однако доступ по HTTP должен сохраняться, чтобы сертификат мог обновляться.
|
||||||
|
|
||||||
|
Этот способ _не_ поддерживает wildcard‑домены.
|
||||||
|
|
||||||
|
### DNS-сертификат
|
||||||
|
|
||||||
|
Сертификат, подтверждённый по DNS, требует использования плагина DNS‑провайдера. Такой провайдер создаст временные записи в вашем домене, затем Let's Encrypt проверит эти записи, чтобы убедиться, что вы владелец домена, и при успехе выпустит сертификат.
|
||||||
|
|
||||||
|
Для запроса такого сертификата предварительно создавать _прокси‑хост_ не требуется. Также не нужен доступ по HTTP для вашего _прокси‑хоста_.
|
||||||
|
|
||||||
|
Этот способ _поддерживает_ wildcard‑домены.
|
||||||
|
|
||||||
|
### Свой сертификат
|
||||||
|
|
||||||
|
Используйте этот вариант, чтобы загрузить собственный SSL‑сертификат, выданный вашим удостоверяющим центром (CA).
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user