52531dd41a
Auth was effectively broken for every login path: the Telegram handler built a Set-Cookie header but never attached it to the response, and Ghost Protocol (seed) login never set a cookie at all — so billing, the game, and admin actions never actually worked once the frontend was fixed to stop trying to read an HttpOnly cookie from JS. Replaced the bare 10-minute JWT with AuthService::issue_session: a 15-minute access cookie plus a rotating 30-day refresh cookie (opaque token, only its SHA-256 hash stored in the new refresh_sessions table); reusing an already- rotated refresh token now revokes every session for that user. CSRF/CORS origin allowlists moved from a single hardcoded domain to env-configured lists so the same session works across the landing and account subdomains. The bot's WebApp deep links (Личный Кабинет/Тарифы/Синдикат) now bridge through a short-lived bootstrap token exchanged via POST /auth/exchange instead of a bare access token in the URL. Nodes: /nodes/routing now serializes the tunnel_* fields, public_key, sni_domain and a one-time session token gated on an active subscription instead of a stub ip/port/protocol list; node provisioning returns 202 immediately and finishes in the background instead of blocking the request for the whole SSH run; a new background task TCP-pings nodes every 60s and flips online/offline itself; admin can now force-restart a node over SSH. Billing: TON exchange rate is cached in Redis (60s) instead of hitting TonAPI on every invoice, and the request/response now actually uses the requested currency and TonAPI's real (uppercase) key casing — previously only USD/RUB were ever requested and the lookup used the wrong case, so non-USD/RUB plans could never price correctly. Cyberhack: the server now generates and stores the board itself and replays the client's raw click path to compute the score, instead of clamping a client-reported number — closes the "final score is whatever the browser sends" hole flagged in the security audit. Frontend: api.ts no longer tries to read the HttpOnly session cookie from JS (that never worked) and instead relies on same-origin credentials plus a silent refresh-and-retry on 401; AdminDashboard's restart/delete actions are wired up; Auth.tsx drops the artificial delays and requires an explicit seed download/confirmation before continuing, since a lost Ghost seed is unrecoverable. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
43 lines
3.7 KiB
Markdown
43 lines
3.7 KiB
Markdown
# Netrunner — фронтенд
|
||
|
||
SPA-клиент (React 19 + TypeScript + Vite) для Netrunner Control Plane. Собирается в
|
||
статику и раздаётся самим бэкендом ([`../src/api.rs`](../src/api.rs) — `ServeDir` с
|
||
SPA-фолбэком на `index.html`), поэтому в проде отдельного веб-сервера для фронта нет.
|
||
|
||
## Экраны (`src/*.tsx`)
|
||
|
||
| Файл | Роут | Что делает |
|
||
|---|---|---|
|
||
| `Auth.tsx` | `/` | Вход: Telegram Login Widget, генерация/логин Ghost-seed (с обязательным скачиванием + подтверждением сохранения seed перед переходом дальше). Хранит терминальный лог событий для киберпанк-эстетики. |
|
||
| `Profile.tsx` | `/profile` | Личный кабинет: баланс, статус подписки, покупка тарифа через TON Connect, реферальная ссылка. Также цель WebApp-ссылок из бота («Личный Кабинет»/«Тарифы»/«Синдикат»). |
|
||
| `HackGame.tsx` | `/hack` | Обёртка над npm-пакетом `cyberhack` (мини-игра). Server-authoritative: сначала `POST /users/hack/start` за доской, сгенерированной сервером, по завершении партии шлёт путь кликов на `POST /users/hack/result` — сервер сам считает очки. |
|
||
| `AdminDashboard.tsx` | `/admin` | Список всех VPN-нод (`GET /admin/nodes`, любой статус) + форма добавления ноды + force-restart/удаление. Доступ ограничен `admin_guard` на бэкенде; сам роут на фронте не проверяет роль (полагается на то, что API откажет не-админу `401/403`). |
|
||
|
||
`App.tsx` — роутинг (`react-router-dom`), провайдер `TonConnectUIProvider` и мост
|
||
`useBootstrapTokenExchange`: если в URL есть `#token=` (пришли из бота), сразу меняет
|
||
его на cookie-сессию через `POST /auth/exchange` и чистит URL.
|
||
`api.ts` — единая обёртка `apiFetch` над `/api/v1/*`: всегда с cookie
|
||
(`credentials: "same-origin"`), при первом `401` тихо пробует `POST /auth/refresh` и
|
||
повторяет запрос один раз, при провале — редирект на `/`.
|
||
|
||
## Аутентификация на фронте
|
||
|
||
Сессия — только HttpOnly-cookie (`nrxp_token`/`nrxp_refresh`), которую полностью
|
||
контролирует сервер; фронт её не читает и не хранит нигде сам (ни `localStorage`, ни
|
||
JS-видимая cookie). Раньше `api.ts` пытался читать `nrxp_token` через `document.cookie`
|
||
для простановки в `Authorization: Bearer`, но сервер ставил её `HttpOnly` — то есть JS
|
||
физически не мог её увидеть, и все "авторизованные" запросы уходили без токена вообще.
|
||
Подробности серверной части — в [`../src/modules/auth/README.md`](../src/modules/auth/README.md).
|
||
|
||
## Разработка
|
||
|
||
```bash
|
||
pnpm install
|
||
pnpm dev # dev-сервер Vite; бэкенд отдельно на :8080 (см. VITE_API_BASE)
|
||
pnpm build # tsc -b && vite build -> dist/
|
||
pnpm lint
|
||
```
|
||
|
||
Переменные окружения (`.env.production` / Vite `import.meta.env`):
|
||
`VITE_API_BASE` (по умолчанию `/api/v1`), `VITE_BOT_USERNAME`.
|