""" Единый модуль аутентификации и создания 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 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 get_client(): """HttpClient с автоопределением стенда по активному токену. Использует detect_endpoint() — пробует dev→test стенды. Если автоопределение не сработало — fallback на NUBES_API_ENDPOINT из конфига.""" token = get_token() endpoint = detect_endpoint(token) or current_app.config["NUBES_API_ENDPOINT"] return HttpClient(endpoint, token) 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) по активному токену. detect_endpoint → stand_name. Если автоопределение не сработало — fallback на NUBES_API_ENDPOINT из конфига.""" token = get_token() endpoint = detect_endpoint(token) or current_app.config["NUBES_API_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:]