""" Единый модуль аутентификации и создания HTTP-клиента. Раньше _client(), _client_id(), _stand(), _token_info() были продублированы в main.py и api_test.py с идентичным или почти идентичным кодом. Теперь всё здесь — один источник правды для всех роутов. Функции (все без аргументов — берут данные из Flask request/current_app): get_token() — токен: cookie → env-переменная get_client() — HttpClient с автоопределением стенда get_client_id() — ClientID из JWT (base64url, без проверки подписи) get_stand() — "dev" / "test" по токену get_token_info() — {email, company, client_id} из JWT для UI get_token_masked() — маскированный токен (abc...xyz) для placeholder """ import base64 import json from flask import request, current_app from api.http_client import HttpClient, detect_endpoint, stand_name, STANDS def get_token(): """Получить активный токен: сначала из cookie пользователя, потом из env. Приоритет: 1. cookie "token" — пользователь ввёл свой токен в форме 2. NUBES_API_TOKEN из env — сервисный токен (для автоматических тестов) Пользовательский токен приоритетнее — он переопределяет сервисный.""" return request.cookies.get("token") or current_app.config["NUBES_API_TOKEN"] def _make_client(*, use_polygon): """Фабрика HttpClient — ЕДИНСТВЕННОЕ место где выбирается endpoint. Args: use_polygon: если True и POLYGON_ENDPOINT задан → polygon. Если False → всегда реальный API (для чтения сервисов/инстансов). Returns: HttpClient с правильным endpoint и токеном. """ token = get_token() # Режим полигона — только если разрешено и переменная задана if use_polygon: polygon = current_app.config.get("POLYGON_ENDPOINT", "") if polygon: return HttpClient(polygon, token) # Реальный API: автоопределение или NUBES_API_ENDPOINT endpoint = current_app.config["NUBES_API_ENDPOINT"] if endpoint in STANDS: endpoint = detect_endpoint(token) or endpoint return HttpClient(endpoint, token) def get_client(): """HttpClient для ОПЕРАЦИЙ (create/modify/delete/run/poll). Если POLYGON_ENDPOINT задан → polygon. Иначе → автоопределение реального стенда. """ return _make_client(use_polygon=True) def get_real_client(): """HttpClient для ЧТЕНИЯ (сервисы, список инстансов). ВСЕГДА реальный API, даже при POLYGON_ENDPOINT. """ return _make_client(use_polygon=False) def get_client_id(): """Извлечь ClientID из payload JWT-токена (base64url, без проверки подписи). JWT состоит из трёх частей: header.payload.signature, разделённых точкой. Нам нужен ТОЛЬКО payload — он в base64url (не base64!). ClientID используется для: - Изоляции данных в БД (runs.client_id) - Изоляции трекера инстансов (/tmp/instances-{clientId}-{stand}.json) - Отображения в UI Безопасность: мы НЕ проверяем подпись — это не нужно. Токен уже проверен Nubes API (detect_endpoint делает реальный запрос).""" token = get_token() try: parts = token.split(".") # [header, payload, signature] if len(parts) >= 2: # base64url → добавляем padding ("==") на случай если длина не кратна 4 payload = base64.urlsafe_b64decode(parts[1] + "==") return json.loads(payload).get("ClientID", "") except Exception: pass # битый токен — не критично, вернём пустую строку return "" def get_stand(): """Определить стенд (dev/test/mock) по активному токену. Если POLYGON_ENDPOINT задан → "polygon". Иначе — автоопределение или stand_name.""" if current_app.config.get("POLYGON_ENDPOINT", ""): return "polygon" token = get_token() endpoint = current_app.config["NUBES_API_ENDPOINT"] if endpoint not in STANDS: s = stand_name(endpoint) return s if s != "?" else "mock" endpoint = detect_endpoint(token) or endpoint return stand_name(endpoint) def get_token_info(): """Извлечь {email, company, client_id} из JWT — для отображения в топбаре UI. Возвращает dict с ключами: email, company, client_id. Если JWT невалиден — возвращает пустой {}.""" token = get_token() try: parts = token.split(".") if len(parts) >= 2: payload = base64.urlsafe_b64decode(parts[1] + "==") d = json.loads(payload) return { "email": d.get("email", ""), "company": d.get("company_name", ""), "client_id": d.get("ClientID", ""), } except Exception: pass return {} def get_token_masked(): """Маскированный env-токен для placeholder в форме: abc...xyz. Используется ТОЛЬКО env-токен (не пользовательский!). Если токен короче 8 символов — возвращает пустую строку.""" token = current_app.config["NUBES_API_TOKEN"] if not token or len(token) < 8: return "" # Первые 4 символа + звёздочки + последние 4 символа return token[:4] + "*" * (len(token) - 8) + token[-4:]