feat: добавлена пометка типа операции (Build/Push) в истории сборок Dockerfile

- Добавлена колонка 'Тип' во все таблицы истории сборок
- Для push операций отображается registry вместо платформ
- Сохранение пользователя при создании push лога
- Исправлена ошибка с logger в push_docker_image endpoint
- Улучшено отображение истории сборок с визуальными индикаторами
This commit is contained in:
Сергей Антропов
2026-02-15 22:59:02 +03:00
parent 23e1a6037b
commit 1fbf9185a2
232 changed files with 38075 additions and 5 deletions

95
app/auth/middleware.py Normal file
View File

@@ -0,0 +1,95 @@
"""
Middleware для проверки аутентификации
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
from fastapi import Request, HTTPException, status
from fastapi.responses import RedirectResponse
from starlette.middleware.base import BaseHTTPMiddleware
from app.auth.security import decode_access_token
import logging
logger = logging.getLogger(__name__)
# Публичные пути, не требующие аутентификации
PUBLIC_PATHS = [
"/login",
"/api/v1/auth/login",
"/logout",
"/api/v1/auth/logout",
"/health",
"/static",
"/api/docs",
"/api/redoc",
"/openapi.json",
"/api/v1/stats", # Статистика доступна без аутентификации
"/api/v1/dockerfiles/build-logs/webhook", # Webhook для получения логов от builder
"/api/v1/dockerfiles/build-logs/recent" # Получение последних логов сборки (может использоваться на странице сборки)
]
class AuthMiddleware(BaseHTTPMiddleware):
"""Middleware для проверки аутентификации"""
async def dispatch(self, request: Request, call_next):
path = request.url.path
# Пропускаем публичные пути (проверяем точное совпадение или начало пути)
is_public = False
for public_path in PUBLIC_PATHS:
if path == public_path or path.startswith(public_path + "/") or path.startswith(public_path):
is_public = True
break
if is_public:
return await call_next(request)
# Проверка токена из cookie или заголовка
token = None
# Проверяем cookie
if request.cookies and "access_token" in request.cookies:
token = request.cookies.get("access_token")
# Проверяем заголовок Authorization
elif "authorization" in request.headers:
auth_header = request.headers.get("authorization", "")
if auth_header and auth_header.startswith("Bearer "):
token = auth_header.replace("Bearer ", "")
# Если токена нет, перенаправляем на страницу входа
if not token:
if path.startswith("/api/"):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Требуется аутентификация"
)
return RedirectResponse(url="/login", status_code=302)
# Проверяем токен
try:
payload = decode_access_token(token)
if payload is None:
# Токен невалидный или истек
if path.startswith("/api/"):
# Для API запросов возвращаем JSON с ошибкой
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Токен аутентификации истек или недействителен"
)
# Для HTML запросов перенаправляем на страницу входа
return RedirectResponse(url="/login?expired=1", status_code=302)
except Exception as e:
# Ошибка при декодировании токена
logger.warning(f"Error decoding token: {e}")
if path.startswith("/api/"):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Ошибка проверки токена аутентификации"
)
return RedirectResponse(url="/login?expired=1", status_code=302)
# Добавляем пользователя в request state
request.state.user = payload.get("sub")
return await call_next(request)