Files
netrunner-backend/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

92 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Netrunner Control Plane
Бэкенд VPN-сервиса «Netrunner»: авторизация (Telegram / анонимный «Ghost Protocol»),
биллинг подписок через TON, реферальная программа, оркестрация VPN-нод по SSH,
Telegram-бот как второй канал входа и встроенная раздача SPA-фронтенда.
Стек: Rust (axum, sqlx/Postgres, teloxide, tokio) + React/TypeScript (Vite) фронтенд,
собранные в единый Docker-образ.
## Структура репозитория
| Путь | Что там | Подробнее |
|---|---|---|
| [`src/`](src) | Rust-бэкенд (control plane) | см. ниже |
| [`src/modules/`](src/modules/README.md) | Бизнес-модули (auth, billing, nodes, referrals, users) | [README](src/modules/README.md) |
| [`src/db/`](src/db/README.md) | Модели и репозиторий (доступ к Postgres) | [README](src/db/README.md) |
| [`frontend/`](frontend/README.md) | React/Vite SPA (личный кабинет, игра, админка нод) | [README](frontend/README.md) |
| [`migrations/`](migrations) | SQL-миграции (sqlx) | — |
| [`templates/init_node.sh`](templates/init_node.sh) | Bash-скрипт инициализации новой VPN-ноды, исполняется по SSH | — |
| [`tests/`](tests) | Интеграционные тесты репозитория (`sqlx::test`, требуют Postgres) | — |
Инфраструктура и процесс деплоя подробно описаны в [`DEPLOYMENT.md`](DEPLOYMENT.md);
список закрытых и остаточных угроз безопасности — в [`SECURITY_AUDIT.md`](SECURITY_AUDIT.md).
## Архитектура бэкенда
Слоями, сверху вниз:
1. **`main.rs`** — точка входа: поднимает пул Postgres, собирает `ApiState`
(репозиторий + сервисы всех модулей) и параллельно запускает HTTP API, Telegram-бота
и фоновые задачи биллинга до общего сигнала остановки (Ctrl+C / SIGTERM).
2. **`api.rs`** — сборка axum-роутера: монтирует контроллеры модулей, rate-limiting
(`tower_governor`), CORS, Prometheus-метрики (`/metrics`), health-пробы (`/health`,
`/health/ready`) и раздачу статики фронтенда как SPA-фолбэк.
3. **`modules/*/controller.rs`** — HTTP-контракты (axum-роуты, JSON DTO), тонкий слой
без бизнес-логики.
4. **`modules/*/service.rs`** — бизнес-логика модуля.
5. **`db::repository::AppRepository`** — трейт доступа к БД; единственная реализация —
`PgRepository` (Postgres/sqlx). Сервисы знают только о трейте, поэтому теоретически
подменяемы в тестах без реальной БД.
6. **`bot.rs`** — Telegram-бот (teloxide), использует те же сервисы, что и HTTP API;
кнопки «Личный Кабинет»/«Тарифы»/«Синдикат» ведут в ЛК через общий WebApp-мост
(короткоживущий bootstrap-токен в URL, фронт меняет его на cookie-сессию через
`POST /auth/exchange`).
7. **`tasks.rs`** — два фоновых цикла: сброс просроченных подписок + сверка блокчейна
на предмет потерянных платежей, и health-check VPN-нод (TCP-пинг раз в 60с,
переоценка `online`/`offline`).
8. **`error.rs`** — единый `AppError` с `IntoResponse`: маппинг в HTTP-статус,
безопасное сообщение наружу и подробности только в логи/метрики.
## Авторизация: единая cookie-сессия для лендинга и ЛК
Один способ входа (Telegram Login Widget / анонимный Ghost Protocol / bootstrap-мост
из бота) выдаёт одну и ту же пару cookie: короткий access-JWT (`nrxp_token`, 15 мин) и
ротируемый непрозрачный refresh-токен (`nrxp_refresh`, 30 дней, хеш хранится в
`refresh_sessions`, путь строго `/api/v1/auth/refresh`). Обе — `HttpOnly; Secure;
SameSite=Lax`; в проде дополнительно `Domain=.netrunner-vpn.com`, поэтому одна и та же
сессия работает и на лендинге (`netrunner-vpn.com`), и на ЛК (`account.netrunner-vpn.com`)
без единого байта в `localStorage` или JS-читаемых cookie. Подробности — в
[`src/modules/auth/README.md`](src/modules/auth/README.md).
## Быстрый старт (локальная разработка)
```bash
cp .env.example .env # заполнить секреты (JWT_SECRET, ARGON_SALT, TELOXIDE_TOKEN, ...)
make dev # docker compose: db + redis + app с hot-reload (cargo watch)
```
Приложение поднимется на `http://localhost:8080` (`/health`, `/health/ready`).
Остальные команды — `make help`.
## Nodes, Cyberhack, биллинг — текущее состояние
- **`GET /nodes/routing`** отдаёт полный `TunnelConfig`: `tunnel_*`-поля,
`public_key`/`sni_domain` ноды и одноразовый (60с) session-токен, который
выдаётся только при активной подписке юзера.
- **Health-check VPN-нод** — фоновая задача (`src/tasks.rs`, TCP-пинг раз в 60с)
переоценивает `online`/`offline` сама; ручной `POST /admin/nodes/{id}/restart`
перезапускает прокси-контейнер на ноде по SSH-ключу.
- **Провижининг ноды асинхронный:** `POST /admin/nodes` сразу отвечает `202
Accepted` со статусом `provisioning`, SSH-деплой идёт в фоне.
- **Курс TON кешируется в Redis** (`ton_rate:<currency>`, TTL 60с) —
`BillingService::initiate_payment` бьёт TonAPI только на промах кеша.
- **Cyberhack — server-authoritative.** Доску генерирует и хранит сервер
(`src/modules/users/cyberhack.rs`, порт алгоритма из `Cyberhack/src/lib.rs`);
клиент присылает не итоговый счёт, а сырой путь кликов, который сервер сам
реплеит и валидирует (`POST /users/hack/start` → `POST /users/hack/result`).
- **Telegram-бот:** «Тарифы» и «Синдикат» ведут в тот же ЛК, что и «Личный
Кабинет» — единый WebApp-мост (см. выше).
- **`AdminDashboard.tsx`:** кнопки «Force Restart» и удаления ноды подключены
к соответствующим ручкам бэкенда.