Unify auth into refresh-token cookie sessions; server-authoritative nodes and Cyberhack
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>
This commit is contained in:
+22
-11
@@ -9,19 +9,28 @@
|
||||
- `GET /nodes` — список активных нод (`status = 'online'`), отсортирован по
|
||||
возрастанию `current_load` — то есть новые юзеры естественным образом идут на самые
|
||||
свободные сервера.
|
||||
- `GET /nodes/stats` — агрегаты для лендинга (страна, загрузка, статус). **`uptime` —
|
||||
заглушка**, см. TODO в `controller.rs`.
|
||||
- `GET /nodes/routing` — конфиг для клиентского приложения (Tauri/mobile). **Сейчас
|
||||
неполный** — см. TODO в `controller.rs` и в корневом README.
|
||||
- `GET /nodes/stats` — агрегаты для лендинга (страна, загрузка, статус, `last_seen`).
|
||||
- `GET /nodes/routing` — полный `TunnelConfig` для клиентского приложения: `ip/port`,
|
||||
`tunnel_address/prefix/dns/mtu`, `public_key`/`sni_domain` ноды и одноразовый (60с)
|
||||
`session_token` (HMAC-подписанный, `NodeService::generate_session_token`) — `null`,
|
||||
если у юзера нет активной подписки.
|
||||
- `admin_router` (за `auth_guard` **и** `admin_guard`, в таком порядке — `admin_guard`
|
||||
ожидает, что `auth_guard` уже положил юзера в extensions):
|
||||
- `POST /admin/nodes` — добавить и заprovisioning новую ноду.
|
||||
- `GET /admin/nodes` — список **всех** нод независимо от статуса (в отличие от
|
||||
публичного `GET /nodes`, который отдаёт только `online`) — нужен админке, чтобы видеть
|
||||
ноды в `provisioning`/`offline`.
|
||||
- `POST /admin/nodes` — сразу вставляет ноду со статусом `provisioning` и отвечает
|
||||
`202 Accepted`; реальный SSH-провижининг идёт в фоне (`spawn_provisioning`).
|
||||
- `DELETE /admin/nodes/{id}` — удалить ноду из БД (саму VPS-машину это не трогает).
|
||||
- `POST /admin/nodes/{id}/restart` — перезапускает прокси-контейнер на ноде по SSH
|
||||
(уже по мастер-ключу, не по паролю — см. ниже).
|
||||
|
||||
## Провижининг (`service.rs::provision_node_via_ssh`)
|
||||
## Провижининг (`service.rs`)
|
||||
|
||||
Синхронная (в смысле — блокирующий поток через `spawn_blocking`, но HTTP-запрос ждёт её
|
||||
целиком, см. TODO) операция по `ssh2`:
|
||||
`add_node` вставляет ноду в БД со статусом `provisioning` и сразу возвращает управление
|
||||
контроллеру (`202 Accepted`); сам SSH-провижининг (`provision_node_via_ssh`) выполняется в
|
||||
фоне (`tokio::spawn` + `spawn_blocking` для блокирующего `ssh2`), по завершении переводит
|
||||
ноду в `online` (успех) или `offline` (провал, с логом причины):
|
||||
|
||||
1. Подключается к `ip:22` с таймаутом 15с, логинится по паролю root (временный пароль,
|
||||
передаётся один раз при создании ноды).
|
||||
@@ -30,9 +39,11 @@
|
||||
3. Прописывает публичный ключ control plane (`keys/netrunner_master_ed25519.pub`,
|
||||
создаётся заранее, см. `main.rs::ensure_master_ssh_key`) в `authorized_keys` и **жёстко
|
||||
выключает парольный SSH-вход** (hardening-скрипт внутри метода) — после провижининга
|
||||
зайти на ноду можно только по ключу.
|
||||
зайти на ноду можно только по ключу. Именно поэтому `restart_node` подключается уже
|
||||
по ключу (`connect_ssh_with_key`), а не по паролю, которого больше нет.
|
||||
4. Не более 3 одновременных провижинингов (`Semaphore::new(3)`), чтобы не заDDoSить сам
|
||||
control plane при массовом добавлении нод.
|
||||
|
||||
Одновременно с провижинингом только `add_vpn_node` пишет запись в БД — если SSH-шаг
|
||||
упал, запись не создаётся вовсе (не остаётся полуживых нод в базе).
|
||||
Health-check (`src/tasks.rs`, раз в `NODE_HEALTH_CHECK_INTERVAL` секунд, по умолчанию 60)
|
||||
TCP-пингует каждую не-`provisioning` ноду и переоценивает `online`/`offline` сам — control
|
||||
plane больше не может бесконечно долго считать мёртвую ноду живой.
|
||||
|
||||
Reference in New Issue
Block a user