From f8cd9149c314bb0bae077f05ec9a56e82b7b2fa2 Mon Sep 17 00:00:00 2001 From: arkonsadter Date: Wed, 18 Mar 2026 19:03:29 +0600 Subject: [PATCH] Frontend API config and SSO toggles --- backend/oidc_config.py | 66 ++++++++++++++++------ frontend/.env | 2 +- frontend/.env.example | 7 ++- frontend/.env.local.example | 6 +- frontend/src/components/Auth.jsx | 4 +- frontend/src/components/UserManagement.jsx | 3 +- frontend/src/config.js | 18 +++--- 7 files changed, 69 insertions(+), 37 deletions(-) diff --git a/backend/oidc_config.py b/backend/oidc_config.py index 7964814..140c1aa 100644 --- a/backend/oidc_config.py +++ b/backend/oidc_config.py @@ -4,28 +4,58 @@ import os from typing import Dict, Any -# Конфигурация провайдеров OpenID Connect -OIDC_PROVIDERS = { - "zitadel": { - "name": "ZITADEL", - "client_id": os.getenv("ZITADEL_CLIENT_ID", ""), - "client_secret": os.getenv("ZITADEL_CLIENT_SECRET", ""), - "server_metadata_url": os.getenv("ZITADEL_ISSUER", "") + "/.well-known/openid-configuration", - "issuer": os.getenv("ZITADEL_ISSUER", ""), - "scopes": ["openid", "email", "profile"], - "icon": "🔐", - "color": "bg-purple-600 hover:bg-purple-700" + +def _is_truthy(value: str) -> bool: + """Безопасный парсинг bool из переменных окружения.""" + return value.strip().lower() in {"1", "true", "yes", "on"} + + +def _is_config_value_set(value: str) -> bool: + """Проверка, что значение реально задано, а не заглушка.""" + normalized = value.strip().lower() + return normalized not in {"", "none", "null", "undefined"} + + +def is_sso_enabled() -> bool: + """Глобальный флаг включения SSO через env.""" + # По умолчанию SSO включён, чтобы не ломать существующее поведение. + raw = os.getenv("SSO_ENABLED", "true") + return _is_truthy(raw) + + +def get_oidc_providers() -> Dict[str, Dict[str, Any]]: + """Собрать конфигурацию OpenID Connect провайдеров из env.""" + issuer = os.getenv("ZITADEL_ISSUER", "") + return { + "zitadel": { + "name": "ZITADEL", + "client_id": os.getenv("ZITADEL_CLIENT_ID", ""), + "client_secret": os.getenv("ZITADEL_CLIENT_SECRET", ""), + "server_metadata_url": issuer.rstrip("/") + "/.well-known/openid-configuration" if issuer else "", + "issuer": issuer, + "scopes": ["openid", "email", "profile"], + "icon": "🔐", + "color": "bg-purple-600 hover:bg-purple-700" + } } -} + + +# Для обратной совместимости с импортами из других модулей +OIDC_PROVIDERS = get_oidc_providers() + def get_enabled_providers() -> Dict[str, Dict[str, Any]]: - """Получить список включённых провайдеров (с настроенными client_id)""" - enabled = {} - for provider_id, config in OIDC_PROVIDERS.items(): - if config.get("client_id") and config.get("issuer"): + """Получить список включённых провайдеров (с настроенным client_id).""" + if not is_sso_enabled(): + return {} + + enabled: Dict[str, Dict[str, Any]] = {} + for provider_id, config in get_oidc_providers().items(): + if _is_config_value_set(config.get("client_id", "")) and _is_config_value_set(config.get("issuer", "")): enabled[provider_id] = config return enabled + def get_redirect_uri(provider_id: str, base_url: str = "http://localhost:8000") -> str: - """Получить redirect URI для провайдера""" - return f"{base_url}/api/auth/oidc/{provider_id}/callback" \ No newline at end of file + """Получить redirect URI для провайдера.""" + return f"{base_url}/api/auth/oidc/{provider_id}/callback" diff --git a/frontend/.env b/frontend/.env index 097dbc2..292a14c 100644 --- a/frontend/.env +++ b/frontend/.env @@ -1 +1 @@ -VITE_API_URL=http://26.62.117.104:8000 +VITE_API_URL= diff --git a/frontend/.env.example b/frontend/.env.example index 606a002..a4e0e91 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -1,3 +1,4 @@ -# API URL (необязательно, по умолчанию определяется автоматически) -# Раскомментируйте и укажите ваш IP для удаленного доступа -# VITE_API_URL=http://26.123.45.67:8000 +# API URL: +# - пусто: same-origin (/api), рекомендуется для production с nginx proxy +# - http://localhost:4546: локальный backend +VITE_API_URL= diff --git a/frontend/.env.local.example b/frontend/.env.local.example index 60703f0..7aa8f91 100644 --- a/frontend/.env.local.example +++ b/frontend/.env.local.example @@ -1,10 +1,10 @@ # Создайте файл .env.local и раскомментируйте нужную строку # Для локального использования (по умолчанию) -# VITE_API_URL=http://localhost:8000 +# VITE_API_URL=http://localhost:4546 # Для Radmin VPN (замените на ваш IP) -# VITE_API_URL=http://26.62.117.104:8000 +# VITE_API_URL=http://26.62.117.104:4546 # Для Hamachi (замените на ваш IP) -# VITE_API_URL=http://25.123.45.67:8000 +# VITE_API_URL=http://25.123.45.67:4546 diff --git a/frontend/src/components/Auth.jsx b/frontend/src/components/Auth.jsx index 11a283b..73cd83e 100644 --- a/frontend/src/components/Auth.jsx +++ b/frontend/src/components/Auth.jsx @@ -41,7 +41,7 @@ export default function Auth({ onLogin }) { try { await onLogin(username, password, isLogin); } catch (err) { - setError(err.message || 'Ошибка авторизации'); + setError(err?.response?.data?.detail || err.message || 'Ошибка авторизации'); } finally { setLoading(false); } @@ -182,7 +182,7 @@ export default function Auth({ onLogin }) { {isLogin && (

Учётные данные по умолчанию:

-

none / none

+

admin / Admin

)} diff --git a/frontend/src/components/UserManagement.jsx b/frontend/src/components/UserManagement.jsx index 4ba79ad..b633b19 100644 --- a/frontend/src/components/UserManagement.jsx +++ b/frontend/src/components/UserManagement.jsx @@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'; import { Users, Shield, Ban, Trash2, UserCheck, Server } from 'lucide-react'; import axios from 'axios'; import { notify } from './NotificationSystem'; +import { API_URL } from '../config'; const UserManagement = ({ token, currentUser }) => { const [users, setUsers] = useState([]); @@ -10,8 +11,6 @@ const UserManagement = ({ token, currentUser }) => { const [showRoleModal, setShowRoleModal] = useState(false); const [showAccessModal, setShowAccessModal] = useState(false); - const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000'; - // Загрузка пользователей const loadUsers = async () => { try { diff --git a/frontend/src/config.js b/frontend/src/config.js index 3a0871a..ce91233 100644 --- a/frontend/src/config.js +++ b/frontend/src/config.js @@ -1,21 +1,23 @@ // Автоматически определяем API URL const getApiUrl = () => { - // Если задана переменная окружения, используем её - if (import.meta.env.VITE_API_URL) { - return import.meta.env.VITE_API_URL; + // Если переменная задана даже пустой строкой, используем её как явный override. + // Пустая строка = same-origin (/api через nginx proxy). + if (Object.prototype.hasOwnProperty.call(import.meta.env, 'VITE_API_URL')) { + const value = import.meta.env.VITE_API_URL || ''; + return value.replace(/\/$/, ''); } - // Иначе используем текущий хост с портом 8000 + // Иначе используем текущий хост с портом 4546 const protocol = window.location.protocol; const hostname = window.location.hostname; - // Если localhost, используем localhost:8000 + // Если localhost, используем localhost:4546 if (hostname === 'localhost' || hostname === '127.0.0.1') { - return `${protocol}//localhost:8000`; + return `${protocol}//localhost:4546`; } - // Для удаленного доступа используем IP:8000 - return `${protocol}//${hostname}:8000`; + // Для удаленного доступа используем IP:4546 + return `${protocol}//${hostname}:4546`; }; export const API_URL = getApiUrl();