Было: https://polygon.../api/v1/svc/test/instances → 404 Стало: https://polygon.../test/api/v1/svc/instances → OK Замена /api/v1/svc → /{stand}/api/v1/svc.
169 lines
7.7 KiB
Python
169 lines
7.7 KiB
Python
"""
|
||
Единый модуль аутентификации и создания HTTP-клиента.
|
||
|
||
Раньше _client(), _client_id(), _stand(), _token_info() были продублированы
|
||
в main.py и api_test.py с идентичным или почти идентичным кодом.
|
||
Теперь всё здесь — один источник правды для всех роутов.
|
||
|
||
Режимы работы (2026-08-03):
|
||
- Эмуляция (mode=polygon): всё в полигон, стенд из селектора (dev/test/prod)
|
||
- Облако (mode=cloud): реальный API, стенд автоопределяется по токену
|
||
|
||
Cookie (устанавливаются main.py:action=set_mode):
|
||
- mode — "polygon" (по умолчанию) или "cloud"
|
||
- polygon_stand — "test" (по умолчанию), "dev", "prod"
|
||
|
||
Функции (все без аргументов — берут данные из Flask request/current_app):
|
||
get_mode() — "polygon" / "cloud"
|
||
get_polygon_stand() — "dev" / "test" / "prod"
|
||
get_token() — токен: cookie → env (в эмуляции только env)
|
||
get_client() — HttpClient с учётом режима и стенда
|
||
get_real_client() — алиас get_client() (историческая совместимость)
|
||
get_client_id() — ClientID из JWT (base64url, без проверки подписи)
|
||
get_stand() — "polygon_test" / "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_mode():
|
||
"""Режим: 'polygon' (эмуляция) или 'cloud' (реальное облако).
|
||
|
||
Если POLYGON_ENDPOINT не задан в конфиге — всегда облако (селектор не нужен).
|
||
Иначе — из cookie, по умолчанию эмуляция."""
|
||
if not current_app.config.get("POLYGON_ENDPOINT", ""):
|
||
return "cloud"
|
||
return request.cookies.get("mode", "polygon")
|
||
|
||
|
||
def get_polygon_stand():
|
||
"""Стенд полигона: 'test' (по умолчанию), 'dev', 'prod'.
|
||
|
||
Используется ТОЛЬКО в режиме эмуляции — для построения URL и load_service_ids."""
|
||
return request.cookies.get("polygon_stand", "test")
|
||
|
||
|
||
def get_token():
|
||
"""Получить активный токен.
|
||
|
||
В эмуляции — ТОЛЬКО env-токен (пользовательский ввод дезактивирован).
|
||
В облаке — cookie → env (как раньше)."""
|
||
if get_mode() == "polygon":
|
||
return current_app.config["NUBES_API_TOKEN"]
|
||
return request.cookies.get("token") or current_app.config["NUBES_API_TOKEN"]
|
||
|
||
|
||
def get_client():
|
||
"""HttpClient с учётом режима и стенда.
|
||
|
||
Эмуляция: polygon_url + "/" + stand → https://polygon.../{stand}/api/v1/svc
|
||
Облако: реальный API с автоопределением стенда (dev/test по токену)."""
|
||
token = get_token()
|
||
mode = get_mode()
|
||
|
||
if mode == "polygon":
|
||
polygon = current_app.config.get("POLYGON_ENDPOINT", "")
|
||
stand = get_polygon_stand()
|
||
# POLYGON_ENDPOINT = "https://polygon.../api/v1/svc"
|
||
# Стенд ВСТАВЛЯЕТСЯ перед /api/v1/svc:
|
||
# "https://polygon.../api/v1/svc" → "https://polygon.../test/api/v1/svc"
|
||
# Простая замена "/api/v1/svc" → "/{stand}/api/v1/svc"
|
||
return HttpClient(polygon.replace("/api/v1/svc", f"/{stand}/api/v1/svc"), token)
|
||
|
||
# Облако: 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_real_client():
|
||
"""Алиас get_client() — историческая совместимость.
|
||
|
||
Раньше get_real_client() всегда ходил в реальный API (для сервисов).
|
||
Теперь в эмуляции сервисы тоже из полигона → обе функции идентичны.
|
||
Оставлено чтобы не ломать все места вызова."""
|
||
return get_client()
|
||
|
||
|
||
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():
|
||
"""Определить стенд.
|
||
|
||
Эмуляция: "polygon_{dev|test|prod}" — для load_service_ids и трекера.
|
||
Облако: автоопределение или stand_name (dev/test/mock)."""
|
||
mode = get_mode()
|
||
if mode == "polygon":
|
||
return f"polygon_{get_polygon_stand()}"
|
||
|
||
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:]
|