This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
- 🚫 **OAuth Error**: Исправлена ошибка "Provider not configured" при пустых переменных окружения OAuth
|
- 🚫 **OAuth Error**: Исправлена ошибка "Provider not configured" при пустых переменных окружения OAuth
|
||||||
- 🔐 **OAuth Session-Free**: Убрана зависимость от SessionMiddleware - OAuth использует только Redis для состояния
|
- 🔐 **OAuth Session-Free**: Убрана зависимость от SessionMiddleware - OAuth использует только Redis для состояния
|
||||||
- 🏷️ **Type Safety**: Исправлена MyPy ошибка с request.client.host - добавлена проверка на None
|
- 🏷️ **Type Safety**: Исправлена MyPy ошибка с request.client.host - добавлена проверка на None
|
||||||
|
- 🔑 **VK OAuth PKCE**: Убрана поддержка PKCE для VK/Yandex/Telegram - эти провайдеры не поддерживают code_challenge
|
||||||
- 🔒 **OAuth Facebook**: Обновлена версия API с v13.0 до v18.0 (актуальная)
|
- 🔒 **OAuth Facebook**: Обновлена версия API с v13.0 до v18.0 (актуальная)
|
||||||
- 🔒 **OAuth Facebook**: Добавлены обязательные scope и параметры безопасности
|
- 🔒 **OAuth Facebook**: Добавлены обязательные scope и параметры безопасности
|
||||||
- 🔒 **OAuth Facebook**: Улучшена обработка ошибок API и валидация ответов
|
- 🔒 **OAuth Facebook**: Улучшена обработка ошибок API и валидация ответов
|
||||||
|
|||||||
@@ -549,13 +549,22 @@ async def oauth_login_http(request: Request) -> JSONResponse | RedirectResponse:
|
|||||||
callback_uri = f"{FRONTEND_URL}oauth/{provider}/callback"
|
callback_uri = f"{FRONTEND_URL}oauth/{provider}/callback"
|
||||||
|
|
||||||
# 🔍 Создаем redirect URL вручную (обходим использование request.session в authlib)
|
# 🔍 Создаем redirect URL вручную (обходим использование request.session в authlib)
|
||||||
authorization_url = await client.create_authorization_url(
|
# VK не поддерживает PKCE, используем code_challenge только для поддерживающих провайдеров
|
||||||
callback_uri,
|
if provider in ["vk", "yandex", "telegram"]:
|
||||||
code_challenge=code_challenge,
|
# Провайдеры без PKCE поддержки
|
||||||
code_challenge_method="S256",
|
authorization_url = await client.create_authorization_url(
|
||||||
state=state,
|
callback_uri,
|
||||||
)
|
state=state,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Провайдеры с PKCE поддержкой (Google, GitHub, Facebook, X)
|
||||||
|
authorization_url = await client.create_authorization_url(
|
||||||
|
callback_uri,
|
||||||
|
code_challenge=code_challenge,
|
||||||
|
code_challenge_method="S256",
|
||||||
|
state=state,
|
||||||
|
)
|
||||||
|
|
||||||
return RedirectResponse(url=authorization_url["url"], status_code=302)
|
return RedirectResponse(url=authorization_url["url"], status_code=302)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -584,21 +593,27 @@ async def oauth_callback_http(request: Request) -> JSONResponse | RedirectRespon
|
|||||||
if not client:
|
if not client:
|
||||||
return JSONResponse({"error": "Provider not configured"}, status_code=400)
|
return JSONResponse({"error": "Provider not configured"}, status_code=400)
|
||||||
|
|
||||||
# 🔍 Получаем code_verifier из Redis вместо request.session
|
|
||||||
code_verifier = oauth_data.get("code_verifier")
|
|
||||||
if not code_verifier:
|
|
||||||
return JSONResponse({"error": "Missing code verifier in OAuth state"}, status_code=400)
|
|
||||||
|
|
||||||
# Получаем authorization code из query параметров
|
# Получаем authorization code из query параметров
|
||||||
code = request.query_params.get("code")
|
code = request.query_params.get("code")
|
||||||
if not code:
|
if not code:
|
||||||
return JSONResponse({"error": "Missing authorization code"}, status_code=400)
|
return JSONResponse({"error": "Missing authorization code"}, status_code=400)
|
||||||
|
|
||||||
# Обмениваем code на токен вручную
|
# 🔍 Обмениваем code на токен - с PKCE или без в зависимости от провайдера
|
||||||
token = await client.fetch_access_token(
|
if provider in ["vk", "yandex", "telegram"]:
|
||||||
authorization_response=str(request.url),
|
# Провайдеры без PKCE поддержки
|
||||||
code_verifier=code_verifier,
|
token = await client.fetch_access_token(
|
||||||
)
|
authorization_response=str(request.url),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Провайдеры с PKCE поддержкой
|
||||||
|
code_verifier = oauth_data.get("code_verifier")
|
||||||
|
if not code_verifier:
|
||||||
|
return JSONResponse({"error": "Missing code verifier in OAuth state"}, status_code=400)
|
||||||
|
|
||||||
|
token = await client.fetch_access_token(
|
||||||
|
authorization_response=str(request.url),
|
||||||
|
code_verifier=code_verifier,
|
||||||
|
)
|
||||||
if not token:
|
if not token:
|
||||||
return JSONResponse({"error": "Failed to get access token"}, status_code=400)
|
return JSONResponse({"error": "Failed to get access token"}, status_code=400)
|
||||||
|
|
||||||
|
|||||||
@@ -251,6 +251,34 @@ class TestOAuthFunctional:
|
|||||||
if isinstance(body, memoryview):
|
if isinstance(body, memoryview):
|
||||||
body = bytes(body)
|
body = bytes(body)
|
||||||
assert b"Missing authorization code" in body
|
assert b"Missing authorization code" in body
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_vk_oauth_without_pkce(self):
|
||||||
|
"""Тест VK OAuth без PKCE (VK не поддерживает code_challenge)"""
|
||||||
|
|
||||||
|
request = MagicMock(spec=Request)
|
||||||
|
request.path_params = {"provider": "vk"}
|
||||||
|
|
||||||
|
mock_client = AsyncMock()
|
||||||
|
# VK должен вызываться без code_challenge
|
||||||
|
mock_client.create_authorization_url = AsyncMock(return_value={
|
||||||
|
"url": "https://oauth.vk.com/authorize?client_id=test&state=abc123"
|
||||||
|
})
|
||||||
|
|
||||||
|
with patch("auth.oauth.oauth.create_client", return_value=mock_client), \
|
||||||
|
patch("auth.oauth.store_oauth_state") as mock_store:
|
||||||
|
|
||||||
|
response = await oauth_login_http(request)
|
||||||
|
|
||||||
|
assert isinstance(response, RedirectResponse)
|
||||||
|
assert response.status_code == 302
|
||||||
|
|
||||||
|
# Проверяем что create_authorization_url вызван БЕЗ code_challenge для VK
|
||||||
|
call_args = mock_client.create_authorization_url.call_args
|
||||||
|
assert "code_challenge" not in call_args.kwargs
|
||||||
|
assert "code_challenge_method" not in call_args.kwargs
|
||||||
|
assert "state" in call_args.kwargs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user