Добавить проверку токена прокси, динамические лимиты трафика и Telegram magic-link вход

Прокси теперь может валидировать клиентские JWT и отчитываться о расходе
трафика через новые internal-эндпоинты (защищены X-Internal-Secret), лимит
на юзера настраивается динамически через admin PATCH без передеплоя прокси.
Заодно реализован задокументированный, но не подключённый флоу входа через
Telegram-бота (magic-link) для десктоп/мобильного приложения, и seed-эндпоинты
теперь возвращают access_token в JSON (не только в cookie) — без этого
Tauri-приложение не могло ими пользоваться.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 00:30:25 +07:00
parent 1a26403afb
commit c8eea8203e
11 changed files with 485 additions and 17 deletions
+20 -1
View File
@@ -29,6 +29,7 @@ use crate::modules::{
auth::{controller as auth_ctrl, service::AuthService},
billing::{controller as bill_ctrl, service::BillingService},
nodes::{controller as node_ctrl, service::NodeService},
proxy_internal,
referrals::{controller as ref_ctrl, service::ReferralService},
users::{controller as user_ctrl, service::UserService},
};
@@ -67,6 +68,9 @@ pub struct ApiState {
/// (CSRF-проверка в `auth::guard`). Отдельно от `CORS_ALLOWED_ORIGINS`, т.к. туда
/// же попадают Bearer-клиенты (Tauri), для которых CSRF неактуален.
pub csrf_allowed_origins: Vec<String>,
/// Общий секрет для `netrunner-proxy` (`X-Internal-Secret`) — не пользовательская
/// авторизация, см. `modules::proxy_internal::internal_secret_guard`.
pub proxy_internal_secret: String,
}
impl Clone for ApiState {
@@ -85,6 +89,7 @@ impl Clone for ApiState {
cookie_domain: self.cookie_domain.clone(),
cookie_secure: self.cookie_secure,
csrf_allowed_origins: self.csrf_allowed_origins.clone(),
proxy_internal_secret: self.proxy_internal_secret.clone(),
}
}
}
@@ -132,7 +137,20 @@ pub fn create_router(state: ApiState, frontend_dir: &str) -> Router {
.route("/config", get(get_public_config))
.layer(GovernorLayer::new(gov_conf));
let admin_routes_v1 = Router::new().nest("/nodes", node_ctrl::admin_router(state.clone()));
let admin_routes_v1 = Router::new()
.nest("/nodes", node_ctrl::admin_router(state.clone()))
.nest("/users", user_ctrl::admin_router(state.clone()));
// Внутренний контракт для прокси-нод — отдельный секрет, не auth_guard
// (прокси не пользовательская сессия) и не участвует в CORS ниже (никогда
// не вызывается из браузера).
let internal_routes = Router::new().nest(
"/internal",
proxy_internal::router().layer(middleware::from_fn_with_state(
state.clone(),
proxy_internal::internal_secret_guard,
)),
);
// Строгий CORS. Список разрешённых origin берём из env (через запятую),
// чтобы помимо веб-фронта пускать и десктоп/мобайл-приложение (Tauri webview).
@@ -156,6 +174,7 @@ pub fn create_router(state: ApiState, frontend_dir: &str) -> Router {
Router::new()
.nest("/api/v1", api_routes_v1)
.nest("/api/v1/admin", admin_routes_v1)
.nest("/api/v1", internal_routes)
// Liveness/readiness пробы для Docker/оркестратора и балансировщика.
.route("/health", get(health_live))
.route("/health/ready", get(health_ready))