From 472174640a62d316eadabf06c1beb8ed3d535582 Mon Sep 17 00:00:00 2001 From: naiba Date: Sun, 31 May 2026 02:04:47 +0000 Subject: [PATCH] feat(settings): add reserved hosts field and surface NAT errors Add the reserved_hosts setting (input + i18n across all locales) so operators behind a reverse proxy can declare public dashboard hostnames members must not register as NAT domains (GHSA-x6fg-52vr-hj4w). Show the backend error message on NAT create/update failure instead of a generic "unexpected error", so a reserved-host rejection is explained. Co-authored-by: cloudcode --- src/components/nat.tsx | 5 +++-- src/locales/de/translation.json | 2 ++ src/locales/en/translation.json | 2 ++ src/locales/es/translation.json | 2 ++ src/locales/id/translation.json | 2 ++ src/locales/it/translation.json | 2 ++ src/locales/ru/translation.json | 2 ++ src/locales/ta/translation.json | 2 ++ src/locales/zh-CN/translation.json | 2 ++ src/locales/zh-TW/translation.json | 2 ++ src/routes/settings.tsx | 16 ++++++++++++++++ src/types/api.ts | 3 +++ 12 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/components/nat.tsx b/src/components/nat.tsx index 209aa3c..f8daa82 100644 --- a/src/components/nat.tsx +++ b/src/components/nat.tsx @@ -82,10 +82,11 @@ export const NATCard: React.FC = ({ data, mutate }) => { } else { await createNAT(values) } - } catch (e) { + } catch (e: unknown) { console.error(e) toast(t("Error"), { - description: t("Results.UnExpectedError"), + description: + e instanceof Error && e.message ? e.message : t("Results.UnExpectedError"), }) return } diff --git a/src/locales/de/translation.json b/src/locales/de/translation.json index 8c6828d..f1dd0cd 100644 --- a/src/locales/de/translation.json +++ b/src/locales/de/translation.json @@ -148,6 +148,8 @@ "Loading": "Laden", "Services": "Services", "DashboardOriginalHost": "Agent Verbindungsadresse [Domainname/IP:port]", + "ReservedHosts": "Reservierte Hosts (kommagetrennt; öffentliche/Reverse-Proxy-Domains, die Mitglieder nicht als NAT-Domain nutzen dürfen)", + "ReservedHostsHint": "Hinter einem Reverse-Proxy sieht das Dashboard seine eigene öffentliche Domain nicht und ist daher nicht automatisch geschützt. Tragen Sie hier jeden öffentlichen Eingangs-Hostnamen ein, sonst könnte ein Mitglied ihn als NAT-Domain registrieren und das Dashboard-Routing übernehmen.", "NavigateTo": "Navigieren", "Offline": "Offline", "Interval": "Intervall", diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index b033d0a..fbd4138 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -142,6 +142,8 @@ "WAF": "Web Application Firewall", "SiteName": "Site Name", "DashboardOriginalHost": "Agent connecting address [domain name/IP:port]", + "ReservedHosts": "Reserved hosts (comma-separated; public/reverse-proxy domains members cannot use as NAT domains)", + "ReservedHostsHint": "Behind a reverse proxy the dashboard cannot see its own public domain, so it is not auto-protected. List every public entry hostname here, or a member could register it as a NAT domain and hijack dashboard routing.", "ConfigTLS": "Use TLS to connect Agent", "LoginFailed": "Login Failed", "BruteForceAttackingToken": "Brute Force Attacking Token", diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index 6652563..a34923e 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -158,6 +158,8 @@ "RequestType": "Tipo de solicitud", "RequestBody": "Cuerpo de la solicitud", "DashboardOriginalHost": "Dirección de conexión del Agent [nombre de dominio/IP:puerto]", + "ReservedHosts": "Hosts reservados (separados por comas; dominios públicos/proxy inverso que los miembros no pueden usar como dominio NAT)", + "ReservedHostsHint": "Detrás de un proxy inverso, el panel no ve su propio dominio público, por lo que no se protege automáticamente. Indique aquí todos los nombres de host de entrada públicos; de lo contrario, un miembro podría registrarlo como dominio NAT y secuestrar el enrutamiento del panel.", "ConfigTLS": "Usar TLS para conectar el Agent", "CustomCodes": "Códigos personalizados (Style y Script)", "CustomCodesDashboard": "Códigos personalizados para el Dashboard", diff --git a/src/locales/id/translation.json b/src/locales/id/translation.json index 6d622af..baa429e 100644 --- a/src/locales/id/translation.json +++ b/src/locales/id/translation.json @@ -139,6 +139,8 @@ "WAF": "Firewall Aplikasi Web", "SiteName": "Nama Situs", "DashboardOriginalHost": "Alamat koneksi Agen [nama domain/IP:port]", + "ReservedHosts": "Host khusus (dipisahkan koma; domain publik/reverse-proxy yang tidak boleh dipakai anggota sebagai domain NAT)", + "ReservedHostsHint": "Di belakang reverse proxy, dasbor tidak melihat domain publiknya sendiri sehingga tidak terlindungi otomatis. Cantumkan setiap host entri publik di sini, atau anggota bisa mendaftarkannya sebagai domain NAT dan membajak perutean dasbor.", "ConfigTLS": "Gunakan TLS untuk menghubungkan Agen", "LoginFailed": "Gagal Masuk", "BruteForceAttackingToken": "Token Serangan Brute Force", diff --git a/src/locales/it/translation.json b/src/locales/it/translation.json index 263518e..435623c 100644 --- a/src/locales/it/translation.json +++ b/src/locales/it/translation.json @@ -142,6 +142,8 @@ "CustomCodes": "Codice personalizzato (stili e script)", "CustomCodesDashboard": "Codice personalizzato per dashboard", "DashboardOriginalHost": "Indirizzo di ancoraggio dell'agente [nome dominio/IP:porta]", + "ReservedHosts": "Host riservati (separati da virgola; domini pubblici/reverse-proxy che i membri non possono usare come dominio NAT)", + "ReservedHostsHint": "Dietro un reverse proxy il pannello non vede il proprio dominio pubblico, quindi non è protetto automaticamente. Elenca qui ogni hostname di ingresso pubblico, altrimenti un membro potrebbe registrarlo come dominio NAT e dirottare il routing del pannello.", "ConfigTLS": "Usa TLS per connettere Agent", "CustomPublicDNSNameserversforDDNS": "Server dei nomi DNS pubblici personalizzati per DDNS", "WebRealIPHeader": "Intestazione della richiesta IP reale del frontend", diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index 246ba70..b5c328e 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -42,6 +42,8 @@ "Credential": "Учетные данные", "CommunityThemeDescription": "Эта тема предоставлена сообществом, используйте на свой страх и риск", "DashboardOriginalHost": "Адрес подключения агента [домен/IP:порт]", + "ReservedHosts": "Зарезервированные хосты (через запятую; публичные/реверс-прокси домены, которые участники не могут использовать как NAT-домен)", + "ReservedHostsHint": "За реверс-прокси панель не видит собственный публичный домен и не защищается автоматически. Укажите здесь все публичные входные хосты, иначе участник сможет зарегистрировать такой домен как NAT-домен и перехватить маршрутизацию панели.", "Error": "Ошибка", "TriggerMode": "Режим срабатывания", "Once": "Один раз", diff --git a/src/locales/ta/translation.json b/src/locales/ta/translation.json index 5efb181..be5473a 100644 --- a/src/locales/ta/translation.json +++ b/src/locales/ta/translation.json @@ -135,6 +135,8 @@ "WAF": "வலை பயன்பாடு ஃபயர்வால்", "SiteName": "தளத்தின் பெயர்", "DashboardOriginalHost": "முகவரியை இணைக்கும் முகவரி [டொமைன் பெயர்/ஐபி: போர்ட்]", + "ReservedHosts": "ஒதுக்கப்பட்ட ஹோஸ்ட்கள் (கமாவால் பிரிக்கப்பட்டது; உறுப்பினர்கள் NAT டொமைனாகப் பயன்படுத்த முடியாத பொது/ரிவர்ஸ்-ப்ராக்ஸி டொமைன்கள்)", + "ReservedHostsHint": "ரிவர்ஸ்-ப்ராக்ஸிக்குப் பின்னால் டாஷ்போர்டு அதன் சொந்த பொது டொமைனைப் பார்க்க முடியாது, எனவே அது தானாகப் பாதுகாக்கப்படாது. அனைத்து பொது நுழைவு ஹோஸ்ட்களையும் இங்கே பட்டியலிடுங்கள்; இல்லையெனில் ஒரு உறுப்பினர் அதை NAT டொமைனாகப் பதிவுசெய்து டாஷ்போர்டு வழித்தடத்தைக் கடத்தலாம்.", "ConfigTLS": "முகவரை இணைக்க TLS ஐப் பயன்படுத்தவும்", "LoginFailed": "உள்நுழைவு தோல்வியடைந்தது", "BruteForceAttackingToken": "மிருகத்தனமான ஆற்றல் டோக்கனைத் தாக்குகிறது", diff --git a/src/locales/zh-CN/translation.json b/src/locales/zh-CN/translation.json index 71b772e..81d4688 100644 --- a/src/locales/zh-CN/translation.json +++ b/src/locales/zh-CN/translation.json @@ -155,6 +155,8 @@ "CustomCodes": "自定义代码(样式和脚本)", "CustomCodesDashboard": "仪表板的自定义代码", "DashboardOriginalHost": "Agent对接地址【域名/IP:端口】", + "ReservedHosts": "保留 Host(逗号分隔;反代/公网域名,成员不可注册为 NAT 域名)", + "ReservedHostsHint": "反代部署下面板看不到自身的对外域名,无法自动保护。请在此列出所有公网入口域名,否则成员可将其注册为 NAT 域名抢占面板路由。", "ConfigTLS": "Agent 使用 TLS 连接", "CustomPublicDNSNameserversforDDNS": "DDNS 的自定义公共 DNS 名称服务器", "WebRealIPHeader": "前端真实IP请求头", diff --git a/src/locales/zh-TW/translation.json b/src/locales/zh-TW/translation.json index b8e5788..d3b1060 100644 --- a/src/locales/zh-TW/translation.json +++ b/src/locales/zh-TW/translation.json @@ -147,6 +147,8 @@ "CustomCodes": "自訂程式碼(樣式和腳本)", "CustomCodesDashboard": "儀表板的自訂程式碼", "DashboardOriginalHost": "Agent對接位址【網域名稱/IP:連接埠】", + "ReservedHosts": "保留 Host(逗號分隔;反代/公網網域,成員不可註冊為 NAT 網域)", + "ReservedHostsHint": "反代部署下面板看不到自身的對外網域,無法自動保護。請在此列出所有公網入口網域,否則成員可將其註冊為 NAT 網域搶佔面板路由。", "ConfigTLS": "Agent 使用 TLS 連線", "CustomPublicDNSNameserversforDDNS": "DDNS 的自訂公共 DNS 名稱伺服器", "WebRealIPHeader": "前端真實IP請求頭", diff --git a/src/routes/settings.tsx b/src/routes/settings.tsx index 4cec5f1..b086533 100644 --- a/src/routes/settings.tsx +++ b/src/routes/settings.tsx @@ -7,6 +7,7 @@ import { Combobox } from "@/components/ui/combobox" import { Form, FormControl, + FormDescription, FormField, FormItem, FormLabel, @@ -44,6 +45,7 @@ const settingFormSchema = z.object({ language: z.string().min(2), user_template: z.string().min(1), install_host: asOptionalField(z.string()), + reserved_hosts: asOptionalField(z.string()), custom_code: asOptionalField(z.string()), custom_code_dashboard: asOptionalField(z.string()), web_real_ip_header: asOptionalField(z.string()), @@ -290,6 +292,20 @@ export default function SettingsPage() { )} /> + ( + + {t("ReservedHosts")} + + + + {t("ReservedHostsHint")} + + + )} + />