Files
core/auth/logout.py

114 lines
4.2 KiB
Python
Raw Permalink Normal View History

[0.9.29] - 2025-09-26 ### 🚨 CRITICAL Security Fixes - **🔒 Open Redirect Protection**: Добавлена строгая валидация redirect_uri против whitelist доменов - **🔒 Rate Limiting**: Защита OAuth endpoints от брутфорса (10 попыток за 5 минут на IP) - **🔒 Logout Endpoint**: Критически важный endpoint для безопасного отзыва httpOnly cookies - **🔒 Provider Validation**: Усиленная валидация OAuth провайдеров с логированием атак - **🚨 GlitchTip Alerts**: Автоматические алерты безопасности в GlitchTip при критических событиях ### 🛡️ Security Modules - **auth/oauth_security.py**: Модуль безопасности OAuth с валидацией и rate limiting + GlitchTip алерты - **auth/logout.py**: Безопасный logout с поддержкой JSON API и browser redirect - **tests/test_oauth_security.py**: Комплексные тесты безопасности (11 тестов) - **tests/test_oauth_glitchtip_alerts.py**: Тесты интеграции с GlitchTip (8 тестов) ### 🔧 OAuth Improvements - **Minimal Flow**: Упрощен до минимума - только httpOnly cookie, нет JWT в URL - **Simple Logic**: Нет error параметра = успех, максимальная простота - **DRY Refactoring**: Устранено дублирование кода в logout и валидации ### 🎯 OAuth Endpoints - **Старт**: `v3.dscrs.site/oauth/{provider}` - с rate limiting и валидацией - **Callback**: `v3.dscrs.site/oauth/{provider}/callback` - безопасный redirect_uri - **Logout**: `v3.dscrs.site/auth/logout` - отзыв httpOnly cookies - **Финализация**: `testing.discours.io/oauth?redirect_url=...` - минимальная схема ### 📊 Security Test Coverage - ✅ Open redirect attack prevention - ✅ Rate limiting protection - ✅ Provider validation - ✅ Safe fallback mechanisms - ✅ Cookie security (httpOnly + Secure + SameSite) - ✅ GlitchTip integration (8 тестов алертов) ### 📝 Documentation - Создан `docs/oauth-minimal-flow.md` - полное описание минимального flow - Обновлена документация OAuth в `docs/auth/oauth.md` - Добавлены security best practices
2025-09-26 21:03:45 +03:00
"""
🔒 OAuth Logout Endpoint - Критически важный для безопасности
Обеспечивает безопасный выход пользователей с отзывом httpOnly cookies.
"""
from starlette.requests import Request
from starlette.responses import JSONResponse, RedirectResponse
from auth.tokens.storage import TokenStorage
from settings import SESSION_COOKIE_NAME
from utils.logger import root_logger as logger
def _clear_session_cookie(response) -> None:
"""🔍 DRY: Единая функция очистки session cookie"""
response.delete_cookie(
SESSION_COOKIE_NAME,
path="/",
domain=".discours.io", # Важно: тот же domain что при установке
)
async def logout_endpoint(request: Request) -> JSONResponse | RedirectResponse:
"""
🔒 Безопасный logout с отзывом httpOnly cookie
Поддерживает как JSON API так и redirect для браузеров.
"""
try:
# 1. Получаем токен из cookie
session_token = request.cookies.get(SESSION_COOKIE_NAME)
if session_token:
# 2. Отзываем сессию в Redis
revoked = await TokenStorage.revoke_session(session_token)
if revoked:
logger.info("✅ Session revoked successfully")
else:
logger.warning("⚠️ Session not found or already revoked")
# 3. Определяем тип ответа
accept_header = request.headers.get("accept", "")
redirect_url = request.query_params.get("redirect_url", "https://testing.discours.io")
if "application/json" in accept_header:
# JSON API ответ
2025-09-26 21:13:23 +03:00
response: JSONResponse | RedirectResponse = JSONResponse(
{"success": True, "message": "Logged out successfully"}
)
[0.9.29] - 2025-09-26 ### 🚨 CRITICAL Security Fixes - **🔒 Open Redirect Protection**: Добавлена строгая валидация redirect_uri против whitelist доменов - **🔒 Rate Limiting**: Защита OAuth endpoints от брутфорса (10 попыток за 5 минут на IP) - **🔒 Logout Endpoint**: Критически важный endpoint для безопасного отзыва httpOnly cookies - **🔒 Provider Validation**: Усиленная валидация OAuth провайдеров с логированием атак - **🚨 GlitchTip Alerts**: Автоматические алерты безопасности в GlitchTip при критических событиях ### 🛡️ Security Modules - **auth/oauth_security.py**: Модуль безопасности OAuth с валидацией и rate limiting + GlitchTip алерты - **auth/logout.py**: Безопасный logout с поддержкой JSON API и browser redirect - **tests/test_oauth_security.py**: Комплексные тесты безопасности (11 тестов) - **tests/test_oauth_glitchtip_alerts.py**: Тесты интеграции с GlitchTip (8 тестов) ### 🔧 OAuth Improvements - **Minimal Flow**: Упрощен до минимума - только httpOnly cookie, нет JWT в URL - **Simple Logic**: Нет error параметра = успех, максимальная простота - **DRY Refactoring**: Устранено дублирование кода в logout и валидации ### 🎯 OAuth Endpoints - **Старт**: `v3.dscrs.site/oauth/{provider}` - с rate limiting и валидацией - **Callback**: `v3.dscrs.site/oauth/{provider}/callback` - безопасный redirect_uri - **Logout**: `v3.dscrs.site/auth/logout` - отзыв httpOnly cookies - **Финализация**: `testing.discours.io/oauth?redirect_url=...` - минимальная схема ### 📊 Security Test Coverage - ✅ Open redirect attack prevention - ✅ Rate limiting protection - ✅ Provider validation - ✅ Safe fallback mechanisms - ✅ Cookie security (httpOnly + Secure + SameSite) - ✅ GlitchTip integration (8 тестов алертов) ### 📝 Documentation - Создан `docs/oauth-minimal-flow.md` - полное описание минимального flow - Обновлена документация OAuth в `docs/auth/oauth.md` - Добавлены security best practices
2025-09-26 21:03:45 +03:00
else:
# Browser redirect
response = RedirectResponse(url=redirect_url, status_code=302)
# 4. Очищаем httpOnly cookie
_clear_session_cookie(response)
logger.info("🚪 User logged out successfully")
return response
except Exception as e:
logger.error(f"❌ Logout error: {e}", exc_info=True)
# Даже при ошибке очищаем cookie
response = JSONResponse({"success": False, "error": "Logout failed"}, status_code=500)
_clear_session_cookie(response)
return response
async def logout_all_sessions(request: Request) -> JSONResponse:
"""
🔒 Отзыв всех сессий пользователя (security endpoint)
Используется при компрометации аккаунта.
"""
try:
# Получаем текущий токен
session_token = request.cookies.get(SESSION_COOKIE_NAME)
if not session_token:
return JSONResponse({"success": False, "error": "No active session"}, status_code=401)
# Получаем user_id из токена
from auth.tokens.sessions import SessionTokenManager
session_manager = SessionTokenManager()
session_data = await session_manager.get_session_data(session_token)
if not session_data:
return JSONResponse({"success": False, "error": "Invalid session"}, status_code=401)
user_id = session_data.get("user_id")
if not user_id:
return JSONResponse({"success": False, "error": "No user ID in session"}, status_code=400)
# Отзываем ВСЕ сессии пользователя
revoked_count = await session_manager.revoke_user_sessions(user_id)
logger.warning(f"🚨 All sessions revoked for user {user_id}: {revoked_count} sessions")
# Очищаем cookie
response = JSONResponse(
{"success": True, "message": f"All sessions revoked: {revoked_count}", "revoked_sessions": revoked_count}
)
_clear_session_cookie(response)
return response
except Exception as e:
logger.error(f"❌ Logout all sessions error: {e}", exc_info=True)
return JSONResponse({"success": False, "error": "Failed to revoke sessions"}, status_code=500)