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>
4.4 KiB
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 (провал, с логом причины):
- Подключается к
ip:22с таймаутом 15с, логинится по паролю root (временный пароль, передаётся один раз при создании ноды). - Гоняет
templates/init_node.sh(обязателен — если файла нет, провижининг падает с ошибкой, а не деплоит полуживую ноду) с переменнымиNODE_IP/GH_TOKEN. - Прописывает публичный ключ control plane (
keys/netrunner_master_ed25519.pub, создаётся заранее, см.main.rs::ensure_master_ssh_key) вauthorized_keysи жёстко выключает парольный SSH-вход (hardening-скрипт внутри метода) — после провижининга зайти на ноду можно только по ключу. Именно поэтомуrestart_nodeподключается уже по ключу (connect_ssh_with_key), а не по паролю, которого больше нет. - Не более 3 одновременных провижинингов (
Semaphore::new(3)), чтобы не заDDoSить сам control plane при массовом добавлении нод.
Health-check (src/tasks.rs, раз в NODE_HEALTH_CHECK_INTERVAL секунд, по умолчанию 60)
TCP-пингует каждую не-provisioning ноду и переоценивает online/offline сам — control
plane больше не может бесконечно долго считать мёртвую ноду живой.