From aa848de31c45cc58f51f778dfb6a679333a3a47c Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Oct 2026 11:30:40 +0000 Subject: [PATCH] Hide the brand from unauthenticated requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GET /ui/brand answered without auth with {"brand":"Forgetting",…}: one request was enough for a scanner to tell a found port is this panel. It now requires the panel JWT (Go and the Node reference alike). The login screen says just «Вход», the page title is neutral until login, and the wordmark loads after sign-in. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013bNGYiDst62wH3ZGTRPFLJ --- README.md | 2 +- awg-ui/index.html | 2 +- awg-ui/server.ts | 6 +++--- awg-ui/src/App.tsx | 13 ++++++++----- internal/ui/server.go | 5 +++-- tools/diffbench/scenario-ui.ts | 1 + 6 files changed, 17 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index fd9ce47..899db98 100644 --- a/README.md +++ b/README.md @@ -187,7 +187,7 @@ TOKEN=$(curl -s -X POST http://HOST:PORT/login \ | Метод | Ответ | |---|---| -| `GET /ui/brand` | `{ "brand": "Forgetting", "channel": "Beta", "version": "0.4.7" }` — **без авторизации**, панель берёт отсюда вордмарк на экране логина | +| `GET /ui/brand` | `{ "brand": "Forgetting", "channel": "Beta", "version": "0.4.7" }` — требует JWT панели (без авторизации выдавал бы панель сканерам). Вордмарк в шапке после входа | | `GET /health` | `{ "status", "server", "ip", "gen", "awg": { "status", "peers", "module", "tools" } }`. `200`, если интерфейс поднят, иначе `503` и `status: "degraded"` | | `GET /awg/status` | `{ "up": true, "peers": 3, "publicKey": "…" }` | diff --git a/awg-ui/index.html b/awg-ui/index.html index 53a3a4a..0fea124 100644 --- a/awg-ui/index.html +++ b/awg-ui/index.html @@ -3,7 +3,7 @@ - AWG Panel + Вход
diff --git a/awg-ui/server.ts b/awg-ui/server.ts index ed2fc4a..0eb3a22 100644 --- a/awg-ui/server.ts +++ b/awg-ui/server.ts @@ -206,9 +206,9 @@ app.post("/logout", requireAuth, (req: Request, res: Response) => { res.json({ ok: true }); }); -// Единственный роут без авторизации: вордмарк нужен уже на экране логина, -// до выдачи JWT. Наружу уходит только имя продукта и версия. -app.get("/ui/brand", (_req: Request, res: Response) => { +// За авторизацией: без неё сканер одним GET узнавал бы, что на порту панель Forgetting. +// Экран логина обходится без вордмарка. +app.get("/ui/brand", requireAuth, (_req: Request, res: Response) => { res.json({ brand: BRAND, channel: CHANNEL, version: VERSION }); }); diff --git a/awg-ui/src/App.tsx b/awg-ui/src/App.tsx index 666fa92..d75c953 100644 --- a/awg-ui/src/App.tsx +++ b/awg-ui/src/App.tsx @@ -185,17 +185,20 @@ export default function App() { return () => clearInterval(t); }, [token, loadNodes]); - // Имя продукта и версия — с сервера, до логина (роут без авторизации). - // Если не ответил, остаётся фолбэк из констант. + // Имя продукта и версия — с сервера, только после логина: роут за авторизацией, чтобы + // панель не опознавалась сканером. Если не ответил, остаётся фолбэк из констант. useEffect(() => { - axios.get('/ui/brand') + if (!token) return; + axios.get('/ui/brand', { headers: { Authorization: `Bearer ${token}` } }) .then(({ data }) => setBrand({ brand: data.brand || BRAND_FALLBACK, channel: data.channel || '', version: data.version || '', })) .catch(() => { /* остаётся фолбэк */ }); - }, []); + }, [token]); + + useEffect(() => { document.title = token ? brand.brand : 'Вход'; }, [token, brand.brand]); // Применять и сохранять тему useEffect(() => { @@ -249,7 +252,7 @@ export default function App() { {!token ? (
- {brand.brand} + Вход

Войдите, чтобы продолжить