Ролевая система вместо привязки к Telegram ---------------------------------------- Раньше единственный способ получить админ-доступ — admin_guard, пускавший по ADMIN_TG_ID или role=="admin" (без сеттера для роли). Заменяет на Owner/Moderator/Partner: - migrations/0005_roles_and_partners.sql — username/password_hash на users (NULL у обычных Ghost/Telegram-юзеров, тот же паттерн, что и у seed_hash/ tg_id), can_manage_nodes, commission_percent, partner_code, referred_by_partner_id; новые таблицы partner_commissions и withdrawal_requests. - auth/guard.rs — admin_guard заменён на owner_guard/staff_guard/ node_manage_guard/partner_guard; ADMIN_TG_ID убран из ApiState и main.rs. - auth/service.rs — hash_password/login_staff со случайной Argon2-солью на каждого юзера (не общий фиксированный ARGON_SALT, который годится для высокоэнтропийных seed-логинов, но не для человеческих паролей). - POST /api/v1/auth/admin/login — вход модератора/партнёра/овнера паролем, переиспользует существующий issue_session(). - --create-owner CLI-флаг (main.rs) со скрытым вводом пароля (rpassword) — вместо ручного SQL для создания первого владельца с VPS-консоли. - src/modules/staff/ — эндпоинты создания модераторов/партнёров, переключения can_manage_nodes, списка заявок на вывод и их approve/reject/paid. - Комиссия партнёра начисляется в BillingService::confirm_payment отдельным шагом после существующей рефералки, пересчитывается через get_plan_price(invoice.plan_name, invoice.currency) — НЕ через invoice.amount напрямую, потому что у провайдеров разная внутренняя единица (TON — nanoTON+2% буфер, CryptoBot — фиатные центы напрямую). - bot.rs — /start p_XXXXXX привязывает покупателя к партнёру по коду (first-wins, до существующих веток numeric-tg_id-рефералки и кода входа). - frontend/ — AdminLogin/AdminLayout/StaffPage/WithdrawalsPage/EarningsPage поверх существующего Vite-админ-фронта (без отдельного проекта). Оплата через @CryptoBot ------------------------ - modules/billing/providers/cryptobot.rs — новый PaymentProvider (Crypto Pay API), плюс попутно исправлены два реальных бага в существующем коде: confirm_payment был жёстко завязан на TON-провайдера при любом инвойсе, и InvoiceRecord не хранил currency, из-за чего TON-курс молча применялся ко всем провайдерам вместо родной валюты инвойса. - bot.rs — экран выбора способа оплаты для CryptoBot-инвойсов. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Netrunner — фронтенд
SPA-клиент (React 19 + TypeScript + Vite) для Netrunner Control Plane. Собирается в
статику и раздаётся самим бэкендом (../src/api.rs — ServeDir с
SPA-фолбэком на index.html), поэтому в проде отдельного веб-сервера для фронта нет.
Экраны (src/*.tsx)
| Файл | Роут | Что делает |
|---|---|---|
Auth.tsx |
/ |
Вход: Telegram Login Widget, генерация/логин Ghost-seed (с обязательным скачиванием + подтверждением сохранения seed перед переходом дальше). Хранит терминальный лог событий для киберпанк-эстетики. |
Profile.tsx |
/profile |
Личный кабинет: баланс, статус подписки, покупка тарифа через TON Connect, реферальная ссылка. Также цель WebApp-ссылок из бота («Личный Кабинет»/«Тарифы»/«Синдикат»). |
HackGame.tsx |
/hack |
Обёртка над npm-пакетом cyberhack (мини-игра). Server-authoritative: сначала POST /users/hack/start за доской, сгенерированной сервером, по завершении партии шлёт путь кликов на POST /users/hack/result — сервер сам считает очки. |
AdminDashboard.tsx |
/admin |
Список всех VPN-нод (GET /admin/nodes, любой статус) + форма добавления ноды + force-restart/удаление. Доступ ограничен admin_guard на бэкенде; сам роут на фронте не проверяет роль (полагается на то, что API откажет не-админу 401/403). |
App.tsx — роутинг (react-router-dom), провайдер TonConnectUIProvider и мост
useBootstrapTokenExchange: если в URL есть #token= (пришли из бота), сразу меняет
его на cookie-сессию через POST /auth/exchange и чистит URL.
api.ts — единая обёртка apiFetch над /api/v1/*: всегда с cookie
(credentials: "same-origin"), при первом 401 тихо пробует POST /auth/refresh и
повторяет запрос один раз, при провале — редирект на /.
Аутентификация на фронте
Сессия — только HttpOnly-cookie (nrxp_token/nrxp_refresh), которую полностью
контролирует сервер; фронт её не читает и не хранит нигде сам (ни localStorage, ни
JS-видимая cookie). Раньше api.ts пытался читать nrxp_token через document.cookie
для простановки в Authorization: Bearer, но сервер ставил её HttpOnly — то есть JS
физически не мог её увидеть, и все "авторизованные" запросы уходили без токена вообще.
Подробности серверной части — в ../src/modules/auth/README.md.
Разработка
pnpm install
pnpm dev # dev-сервер Vite; бэкенд отдельно на :8080 (см. VITE_API_BASE)
pnpm build # tsc -b && vite build -> dist/
pnpm lint
Переменные окружения (.env.production / Vite import.meta.env):
VITE_API_BASE (по умолчанию /api/v1).
Юзернейм бота (для Telegram Login Widget и реферальной ссылки) не build-time
переменная — этот SPA собирается один раз в CI и раздаётся одинаково и прод-, и
dev-стендом, так что зашитое в бандл значение не различалось бы между ними. Вместо
VITE_BOT_USERNAME Auth.tsx/Profile.tsx берут его рантаймом через
GET /api/v1/config (api.ts::getPublicConfig), который читает BOT_USERNAME из
окружения сервера заново на каждый запрос.