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>
92 lines
7.2 KiB
Markdown
92 lines
7.2 KiB
Markdown
# 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» и удаления ноды подключены
|
||
к соответствующим ручкам бэкенда.
|