""" Единый модуль аутентификации и создания 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 get_client(): """HttpClient с автоопределением стенда. Если NUBES_API_ENDPOINT — один из известных стендов (dev/test) → автоопределение через detect_endpoint(). Если NUBES_API_ENDPOINT — любой другой URL (localhost, polygon, кастом) → использовать его напрямую, без detect_endpoint(). Это позволяет направить app-autotest в polygon: NUBES_API_ENDPOINT=http://localhost:5000/api/v1/svc или NUBES_API_ENDPOINT=https://polygon.pythonk8s.dev.nubes.ru/api/v1/svc """ token = get_token() endpoint = current_app.config["NUBES_API_ENDPOINT"] # Автоопределение ТОЛЬКО для известных стендов (dev/test). # Для polygon/localhost/кастомных URL — используем endpoint как есть. if endpoint in STANDS: endpoint = detect_endpoint(token) or 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/mock) по активному токену. Для известных стендов (STANDS) — автоопределение через detect_endpoint(). Для polygon/localhost — stand_name может вернуть "?" (неизвестный URL), заменяем на "mock" чтобы трекер инстансов не сломался. Примечание: для https://polygon.pythonk8s.dev.nubes.ru stand_name вернёт "dev" (подстрока есть в URL) — это ок, трекер создаст /tmp/instances-{clientId}-dev.json.""" token = get_token() endpoint = current_app.config["NUBES_API_ENDPOINT"] if endpoint not in STANDS: # Неизвестный стенд — polygon или localhost 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:]