Files
netrunner-backend/src/modules/nodes/README.md
T
nineap 52531dd41a 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>
2026-07-04 15:29:51 +07:00

4.4 KiB

modules/nodes — Control Plane VPN-нод

Оркестратор физических VPN-серверов: добавление новой ноды по SSH и раздача списка живых нод клиентам/лендингу. Единственный модуль без зависимостей от остальных.

Роуты

  • public_router (за auth_guard, обычный юзер):
    • GET /nodes — список активных нод (status = 'online'), отсортирован по возрастанию current_load — то есть новые юзеры естественным образом идут на самые свободные сервера.
    • 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):
    • 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)

add_node вставляет ноду в БД со статусом provisioning и сразу возвращает управление контроллеру (202 Accepted); сам SSH-провижининг (provision_node_via_ssh) выполняется в фоне (tokio::spawn + spawn_blocking для блокирующего ssh2), по завершении переводит ноду в online (успех) или offline (провал, с логом причины):

  1. Подключается к ip:22 с таймаутом 15с, логинится по паролю root (временный пароль, передаётся один раз при создании ноды).
  2. Гоняет templates/init_node.sh (обязателен — если файла нет, провижининг падает с ошибкой, а не деплоит полуживую ноду) с переменными NODE_IP/GH_TOKEN.
  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 при массовом добавлении нод.

Health-check (src/tasks.rs, раз в NODE_HEALTH_CHECK_INTERVAL секунд, по умолчанию 60) TCP-пингует каждую не-provisioning ноду и переоценивает online/offline сам — control plane больше не может бесконечно долго считать мёртвую ноду живой.