v1.0.90: Steps 1-4 — auth.py, dedup, httponly cookie, _op_results TTL

This commit is contained in:
2026-07-27 22:56:51 +04:00
parent 9085c9088e
commit f6a7bf76c5
4 changed files with 123 additions and 87 deletions
+6 -46
View File
@@ -6,14 +6,12 @@ GET /api/operations/<svc_id> — операции и autotest-инста
Вспомогательные функции:
_resolve_instance_status() — канонический статус (cloud или "creating" из трекера)
_mask() — маскировка токена (env: abc...xyz)
_client_id() — извлечение ClientID из JWT
_token_info() — email, компания, clientId из JWT
"""
from flask import Blueprint, current_app, render_template, request, make_response, jsonify, redirect
from api.http_client import HttpClient, detect_endpoint, create_client, stand_name
from api.auth import get_token, get_client_id, get_token_info, get_token_masked
from operations.get_instances import get_organization, get_instances
from operations.get_services import get_services, get_service_detail
from operations.tracker import list_all as tracker_list
@@ -40,44 +38,6 @@ def _resolve_instance_status(instance, tracked=None):
return "unknown"
def _mask(s):
"""Маскировка токена для показа в placeholder: abc...xyz."""
if not s or len(s) < 8:
return ""
return s[:4] + "*" * (len(s) - 8) + s[-4:]
def _client_id(token):
"""Извлечение ClientID из payload JWT-токена (base64, без проверки подписи)."""
import base64, json
try:
parts = token.split(".") # header.payload.signature
if len(parts) >= 2:
payload = base64.urlsafe_b64decode(parts[1] + "==") # padding
return json.loads(payload).get("ClientID", "")
except Exception:
pass
return ""
def _token_info(token):
"""Извлечение email, company, client_id из JWT для показа в топбаре."""
import base64, json
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 {}
@bp.route("/", methods=["GET", "POST"])
def index():
"""Главная страница: организация, инфраструктура, сервисы, форма токена.
@@ -106,10 +66,10 @@ def index():
ctx = {
"organization": org,
"error": error,
"env_token_masked": _mask(env_token),
"env_token_masked": get_token_masked(),
"has_user_token": bool(user_token),
"client_id": _client_id(active_token),
"token_info": _token_info(active_token),
"client_id": get_client_id(),
"token_info": get_token_info(),
"config": config,
"stand": stand,
"services": services,
@@ -130,7 +90,7 @@ def index():
user_token = request.form.get("token", "")
active_token = user_token or env_token
resp = make_response()
resp.set_cookie("token", user_token, max_age=60*60*24*365) # 1 год
resp.set_cookie("token", user_token, max_age=60*60*24*365, httponly=True, samesite="Strict") # 1 год
resp.headers["Location"] = "/" # редирект на GET (убирает POST из истории)
resp.status_code = 302
return resp
@@ -214,7 +174,7 @@ def api_operations(svc_id):
ops = detail.get("operations", [])
# Трекер: наши autotest-инстансы (изолирован по пользователю и стенду)
tracked = tracker_list(_client_id(active_token), stand_name(endpoint))
tracked = tracker_list(get_client_id(), stand_name(endpoint))
tracked_by_uid = {t["instanceUid"]: t for t in tracked if t["svcId"] == svc_id}
# Все инстансы из облака