This commit is contained in:
2026-01-15 09:32:13 +06:00
parent 14f020e819
commit 303d38f28e
19 changed files with 2072 additions and 14 deletions

View File

@@ -1,6 +1,6 @@
from fastapi import FastAPI, WebSocket, UploadFile, File, HTTPException, Depends, status
from fastapi import FastAPI, WebSocket, UploadFile, File, HTTPException, Depends, status, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from fastapi.responses import FileResponse, RedirectResponse
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import asyncio
import subprocess
@@ -14,9 +14,35 @@ import json
from passlib.context import CryptContext
from jose import JWTError, jwt
from datetime import datetime, timedelta
from authlib.integrations.starlette_client import OAuth
from authlib.common.errors import AuthlibBaseError
import httpx
from dotenv import load_dotenv
from oidc_config import get_enabled_providers, get_redirect_uri, OIDC_PROVIDERS
# Загружаем переменные окружения
load_dotenv()
app = FastAPI(title="MC Panel")
# Инициализация OAuth
oauth = OAuth()
# Регистрация ZITADEL провайдера
enabled_providers = get_enabled_providers()
if "zitadel" in enabled_providers:
config = enabled_providers["zitadel"]
oauth.register(
name="zitadel",
client_id=config["client_id"],
client_secret=config["client_secret"],
server_metadata_url=config["server_metadata_url"],
client_kwargs={"scope": " ".join(config["scopes"])}
)
print(f"✓ ZITADEL провайдер зарегистрирован: {config['issuer']}")
else:
print("⚠ ZITADEL провайдер не настроен. Проверьте .env файл.")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
@@ -139,6 +165,130 @@ def check_server_access(user: dict, server_name: str):
return server_name in user.get("servers", [])
# API для аутентификации
# OpenID Connect endpoints
@app.get("/api/auth/oidc/providers")
async def get_oidc_providers():
"""Получить список доступных OpenID Connect провайдеров"""
providers = {}
for provider_id, config in get_enabled_providers().items():
providers[provider_id] = {
"name": config["name"],
"icon": config["icon"],
"color": config["color"]
}
return providers
@app.get("/api/auth/oidc/{provider}/login")
async def oidc_login(provider: str, request: Request):
"""Начать процесс аутентификации через OpenID Connect"""
if provider not in get_enabled_providers():
raise HTTPException(404, f"Провайдер {provider} не найден или не настроен")
try:
redirect_uri = get_redirect_uri(provider, os.getenv("BASE_URL", "http://localhost:8000"))
return await oauth.create_client(provider).authorize_redirect(request, redirect_uri)
except Exception as e:
raise HTTPException(500, f"Ошибка инициализации OAuth: {str(e)}")
@app.get("/api/auth/oidc/{provider}/callback")
async def oidc_callback(provider: str, request: Request):
"""Обработка callback от OpenID Connect провайдера"""
if provider not in get_enabled_providers():
raise HTTPException(404, f"Провайдер {provider} не найден или не настроен")
try:
client = oauth.create_client(provider)
# Получаем токен от провайдера
token = await client.authorize_access_token(request)
# Получаем данные пользователя
user_data = token.get("userinfo")
if not user_data:
# Если userinfo нет в токене, парсим id_token
user_data = token.get("id_token")
if not user_data:
raise HTTPException(400, "Не удалось получить данные пользователя")
# Создаём или обновляем пользователя
username = create_or_update_oidc_user(user_data, provider)
# Создаём JWT токен для нашей системы
users = load_users()
user = users[username]
access_token = create_access_token({"sub": username, "role": user["role"]})
# Перенаправляем на фронтенд с токеном
frontend_url = os.getenv("FRONTEND_URL", "http://localhost:3000")
return RedirectResponse(f"{frontend_url}/?token={access_token}&username={username}")
except AuthlibBaseError as e:
print(f"OAuth ошибка для {provider}: {str(e)}")
raise HTTPException(400, f"OAuth ошибка: {str(e)}")
except Exception as e:
print(f"Ошибка аутентификации для {provider}: {str(e)}")
raise HTTPException(500, f"Ошибка аутентификации: {str(e)}")
def create_or_update_oidc_user(user_data: dict, provider: str) -> str:
"""Создать или обновить пользователя из OpenID Connect данных"""
users = load_users()
# Генерируем уникальное имя пользователя
email = user_data.get("email", "")
name = user_data.get("name", "")
sub = user_data.get("sub", "")
# Пытаемся использовать email как username
if email:
base_username = email.split("@")[0]
elif name:
base_username = name.replace(" ", "_").lower()
else:
base_username = f"{provider}_user"
# Убираем недопустимые символы
import re
base_username = re.sub(r'[^a-zA-Z0-9_-]', '', base_username)
# Ищем существующего пользователя по OIDC ID
oidc_id = f"{provider}:{sub}"
existing_user = None
for username, user_info in users.items():
if user_info.get("oidc_id") == oidc_id:
existing_user = username
break
if existing_user:
# Обновляем существующего пользователя
users[existing_user]["email"] = email
users[existing_user]["name"] = name
users[existing_user]["picture"] = user_data.get("picture")
save_users(users)
return existing_user
else:
# Создаём нового пользователя
username = base_username
counter = 1
while username in users:
username = f"{base_username}_{counter}"
counter += 1
users[username] = {
"username": username,
"password": "", # Пустой пароль для OIDC пользователей
"role": "user",
"servers": [],
"oidc_id": oidc_id,
"email": email,
"name": name,
"picture": user_data.get("picture"),
"provider": provider,
"created_at": datetime.utcnow().isoformat()
}
save_users(users)
return username
@app.post("/api/auth/register")
async def register(data: dict):
users = load_users()