Files
app-autotest/site/api/auth.py
T
naeel 0a62eb3c58 feat: селектор режима и стенда (v1.2.30)
Новое:
- Селектор режима (🎭 Эмуляция / ☁️ Облако) над версией
- Селектор стенда (DEV/TEST/PROD) в режиме эмуляции
- В эмуляции — всё в полигон (/stand/api/v1/svc)
- В эмуляции — токен дезактивирован
- По умолчанию: эмуляция + TEST

Изменено:
- auth.py: get_mode(), get_polygon_stand(), get_client() с префиксом стенда
- main.py: action=set_mode, единый get_client() вместо real_client/inst_client
- index.html: селекторы, disable токена
- service_list.py: обрезка polygon_ префикса
- api_test.py: get_real_client → get_client

Задокументировано в HISTORY/2026-08-03-session.md.
2026-08-03 08:50:14 +04:00

168 lines
7.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Единый модуль аутентификации и создания 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"
# → "https://polygon.../{stand}/api/v1/svc"
# rstrip на случай trailing slash в конфиге
return HttpClient(f"{polygon.rstrip('/')}/{stand}", 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:]