Hide the brand from unauthenticated requests

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013bNGYiDst62wH3ZGTRPFLJ
This commit is contained in:
Claude
2026-10-28 11:30:40 +00:00
parent 63e84e0df7
commit aa848de31c
6 changed files with 17 additions and 12 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AWG Panel</title>
<title>Вход</title>
</head>
<body>
<div id="root"></div>
+3 -3
View File
@@ -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 });
});
+8 -5
View File
@@ -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 ? (
<div className="login-screen">
<div className="login-card">
<span className="logo-name">{brand.brand}</span>
<span className="logo-name">Вход</span>
<p className="login-sub">Войдите, чтобы продолжить</p>
<input
className="field"