Add SSO
This commit is contained in:
154
backend/main.py
154
backend/main.py
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user