Files
core/auth/logout.py
Untone 05c188df62
Some checks failed
Deploy on push / deploy (push) Failing after 39s
[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

112 lines
4.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
🔒 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 ответ
response = JSONResponse({"success": True, "message": "Logged out successfully"})
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)