2025-05-16 09:23:48 +03:00
|
|
|
|
from starlette.requests import Request
|
2025-06-02 02:56:11 +03:00
|
|
|
|
from starlette.responses import JSONResponse, RedirectResponse, Response
|
2025-05-16 09:23:48 +03:00
|
|
|
|
|
2025-08-17 16:33:54 +03:00
|
|
|
|
from auth.core import verify_internal_auth
|
2025-06-02 21:50:58 +03:00
|
|
|
|
from auth.tokens.storage import TokenStorage
|
[0.9.7] - 2025-08-18
### 🔄 Изменения
- **SQLAlchemy KeyError** - исправление ошибки `KeyError: Reaction` при инициализации
- **Исправлена ошибка SQLAlchemy**: Устранена проблема `InvalidRequestError: When initializing mapper Mapper[Shout(shout)], expression Reaction failed to locate a name (Reaction)`
### 🧪 Тестирование
- **Исправление тестов** - адаптация к новой структуре моделей
- **RBAC инициализация** - добавление `rbac.initialize_rbac()` в `conftest.py`
- **Создан тест для getSession**: Добавлен комплексный тест `test_getSession_cookies.py` с проверкой всех сценариев
- **Покрытие edge cases**: Тесты проверяют работу с валидными/невалидными токенами, отсутствующими пользователями
- **Мокирование зависимостей**: Использование unittest.mock для изоляции тестируемого кода
### 🔧 Рефакторинг
- **Упрощена архитектура**: Убраны сложные конструкции с отложенными импортами, заменены на чистую архитектуру
- **Перемещение моделей** - `Author` и связанные модели перенесены в `orm/author.py`: Вынесены базовые модели пользователей (`Author`, `AuthorFollower`, `AuthorBookmark`, `AuthorRating`) из `orm.author` в отдельный модуль
- **Устранены циклические импорты**: Разорван цикл между `auth.core` → `orm.community` → `orm.author` через реструктуризацию архитектуры
- **Создан модуль `utils/password.py`**: Класс `Password` вынесен в utils для избежания циклических зависимостей
- **Оптимизированы импорты моделей**: Убран прямой импорт `Shout` из `orm/community.py`, заменен на строковые ссылки
### 🔧 Авторизация с cookies
- **getSession теперь работает с cookies**: Мутация `getSession` теперь может получать токен из httpOnly cookies даже без заголовка Authorization
- **Убрано требование авторизации**: `getSession` больше не требует декоратор `@login_required`, работает автономно
- **Поддержка dual-авторизации**: Токен может быть получен как из заголовка Authorization, так и из cookie `session_token`
- **Автоматическая установка cookies**: Middleware автоматически устанавливает httpOnly cookies при успешном `getSession`
- **Обновлена GraphQL схема**: `SessionInfo` теперь содержит поля `success`, `error` и опциональные `token`, `author`
- **Единообразная обработка токенов**: Все модули теперь используют централизованные функции для работы с токенами
- **Улучшена обработка ошибок**: Добавлена детальная валидация токенов и пользователей в `getSession`
- **Логирование операций**: Добавлены подробные логи для отслеживания процесса авторизации
### 📝 Документация
- **Обновлена схема GraphQL**: `SessionInfo` тип теперь соответствует новому формату ответа
- Обновлена документация RBAC
- Обновлена документация авторизации с cookies
2025-08-18 14:25:25 +03:00
|
|
|
|
from auth.utils import extract_token_from_request
|
|
|
|
|
|
from orm.author import Author
|
2025-05-16 09:23:48 +03:00
|
|
|
|
from settings import (
|
|
|
|
|
|
SESSION_COOKIE_HTTPONLY,
|
|
|
|
|
|
SESSION_COOKIE_MAX_AGE,
|
2025-05-29 12:37:39 +03:00
|
|
|
|
SESSION_COOKIE_NAME,
|
|
|
|
|
|
SESSION_COOKIE_SAMESITE,
|
|
|
|
|
|
SESSION_COOKIE_SECURE,
|
2025-05-16 09:23:48 +03:00
|
|
|
|
)
|
[0.9.7] - 2025-08-18
### 🔄 Изменения
- **SQLAlchemy KeyError** - исправление ошибки `KeyError: Reaction` при инициализации
- **Исправлена ошибка SQLAlchemy**: Устранена проблема `InvalidRequestError: When initializing mapper Mapper[Shout(shout)], expression Reaction failed to locate a name (Reaction)`
### 🧪 Тестирование
- **Исправление тестов** - адаптация к новой структуре моделей
- **RBAC инициализация** - добавление `rbac.initialize_rbac()` в `conftest.py`
- **Создан тест для getSession**: Добавлен комплексный тест `test_getSession_cookies.py` с проверкой всех сценариев
- **Покрытие edge cases**: Тесты проверяют работу с валидными/невалидными токенами, отсутствующими пользователями
- **Мокирование зависимостей**: Использование unittest.mock для изоляции тестируемого кода
### 🔧 Рефакторинг
- **Упрощена архитектура**: Убраны сложные конструкции с отложенными импортами, заменены на чистую архитектуру
- **Перемещение моделей** - `Author` и связанные модели перенесены в `orm/author.py`: Вынесены базовые модели пользователей (`Author`, `AuthorFollower`, `AuthorBookmark`, `AuthorRating`) из `orm.author` в отдельный модуль
- **Устранены циклические импорты**: Разорван цикл между `auth.core` → `orm.community` → `orm.author` через реструктуризацию архитектуры
- **Создан модуль `utils/password.py`**: Класс `Password` вынесен в utils для избежания циклических зависимостей
- **Оптимизированы импорты моделей**: Убран прямой импорт `Shout` из `orm/community.py`, заменен на строковые ссылки
### 🔧 Авторизация с cookies
- **getSession теперь работает с cookies**: Мутация `getSession` теперь может получать токен из httpOnly cookies даже без заголовка Authorization
- **Убрано требование авторизации**: `getSession` больше не требует декоратор `@login_required`, работает автономно
- **Поддержка dual-авторизации**: Токен может быть получен как из заголовка Authorization, так и из cookie `session_token`
- **Автоматическая установка cookies**: Middleware автоматически устанавливает httpOnly cookies при успешном `getSession`
- **Обновлена GraphQL схема**: `SessionInfo` теперь содержит поля `success`, `error` и опциональные `token`, `author`
- **Единообразная обработка токенов**: Все модули теперь используют централизованные функции для работы с токенами
- **Улучшена обработка ошибок**: Добавлена детальная валидация токенов и пользователей в `getSession`
- **Логирование операций**: Добавлены подробные логи для отслеживания процесса авторизации
### 📝 Документация
- **Обновлена схема GraphQL**: `SessionInfo` тип теперь соответствует новому формату ответа
- Обновлена документация RBAC
- Обновлена документация авторизации с cookies
2025-08-18 14:25:25 +03:00
|
|
|
|
from storage.db import local_session
|
2025-05-29 12:37:39 +03:00
|
|
|
|
from utils.logger import root_logger as logger
|
2025-05-16 09:23:48 +03:00
|
|
|
|
|
|
|
|
|
|
|
2025-06-02 02:56:11 +03:00
|
|
|
|
async def logout(request: Request) -> Response:
|
2025-05-16 09:23:48 +03:00
|
|
|
|
"""
|
|
|
|
|
|
Выход из системы с удалением сессии и cookie.
|
2025-05-29 12:37:39 +03:00
|
|
|
|
|
2025-05-20 00:00:24 +03:00
|
|
|
|
Поддерживает получение токена из:
|
|
|
|
|
|
1. HTTP-only cookie
|
|
|
|
|
|
2. Заголовка Authorization
|
2025-05-16 09:23:48 +03:00
|
|
|
|
"""
|
[0.9.7] - 2025-08-18
### 🔄 Изменения
- **SQLAlchemy KeyError** - исправление ошибки `KeyError: Reaction` при инициализации
- **Исправлена ошибка SQLAlchemy**: Устранена проблема `InvalidRequestError: When initializing mapper Mapper[Shout(shout)], expression Reaction failed to locate a name (Reaction)`
### 🧪 Тестирование
- **Исправление тестов** - адаптация к новой структуре моделей
- **RBAC инициализация** - добавление `rbac.initialize_rbac()` в `conftest.py`
- **Создан тест для getSession**: Добавлен комплексный тест `test_getSession_cookies.py` с проверкой всех сценариев
- **Покрытие edge cases**: Тесты проверяют работу с валидными/невалидными токенами, отсутствующими пользователями
- **Мокирование зависимостей**: Использование unittest.mock для изоляции тестируемого кода
### 🔧 Рефакторинг
- **Упрощена архитектура**: Убраны сложные конструкции с отложенными импортами, заменены на чистую архитектуру
- **Перемещение моделей** - `Author` и связанные модели перенесены в `orm/author.py`: Вынесены базовые модели пользователей (`Author`, `AuthorFollower`, `AuthorBookmark`, `AuthorRating`) из `orm.author` в отдельный модуль
- **Устранены циклические импорты**: Разорван цикл между `auth.core` → `orm.community` → `orm.author` через реструктуризацию архитектуры
- **Создан модуль `utils/password.py`**: Класс `Password` вынесен в utils для избежания циклических зависимостей
- **Оптимизированы импорты моделей**: Убран прямой импорт `Shout` из `orm/community.py`, заменен на строковые ссылки
### 🔧 Авторизация с cookies
- **getSession теперь работает с cookies**: Мутация `getSession` теперь может получать токен из httpOnly cookies даже без заголовка Authorization
- **Убрано требование авторизации**: `getSession` больше не требует декоратор `@login_required`, работает автономно
- **Поддержка dual-авторизации**: Токен может быть получен как из заголовка Authorization, так и из cookie `session_token`
- **Автоматическая установка cookies**: Middleware автоматически устанавливает httpOnly cookies при успешном `getSession`
- **Обновлена GraphQL схема**: `SessionInfo` теперь содержит поля `success`, `error` и опциональные `token`, `author`
- **Единообразная обработка токенов**: Все модули теперь используют централизованные функции для работы с токенами
- **Улучшена обработка ошибок**: Добавлена детальная валидация токенов и пользователей в `getSession`
- **Логирование операций**: Добавлены подробные логи для отслеживания процесса авторизации
### 📝 Документация
- **Обновлена схема GraphQL**: `SessionInfo` тип теперь соответствует новому формату ответа
- Обновлена документация RBAC
- Обновлена документация авторизации с cookies
2025-08-18 14:25:25 +03:00
|
|
|
|
token = await extract_token_from_request(request)
|
2025-05-16 09:23:48 +03:00
|
|
|
|
|
|
|
|
|
|
# Если токен найден, отзываем его
|
|
|
|
|
|
if token:
|
|
|
|
|
|
try:
|
|
|
|
|
|
# Декодируем токен для получения user_id
|
2025-06-02 02:56:11 +03:00
|
|
|
|
user_id, _, _ = await verify_internal_auth(token)
|
2025-05-16 09:23:48 +03:00
|
|
|
|
if user_id:
|
|
|
|
|
|
# Отзываем сессию
|
2025-06-02 21:50:58 +03:00
|
|
|
|
await TokenStorage.revoke_session(token)
|
2025-05-16 09:23:48 +03:00
|
|
|
|
logger.info(f"[auth] logout: Токен успешно отозван для пользователя {user_id}")
|
|
|
|
|
|
else:
|
|
|
|
|
|
logger.warning("[auth] logout: Не удалось получить user_id из токена")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"[auth] logout: Ошибка при отзыве токена: {e}")
|
2025-05-20 00:00:24 +03:00
|
|
|
|
else:
|
|
|
|
|
|
logger.warning("[auth] logout: Токен не найден в запросе")
|
2025-05-16 09:23:48 +03:00
|
|
|
|
|
|
|
|
|
|
# Создаем ответ с редиректом на страницу входа
|
2025-05-16 10:30:02 +03:00
|
|
|
|
response = RedirectResponse(url="/")
|
2025-05-16 09:23:48 +03:00
|
|
|
|
|
|
|
|
|
|
# Удаляем cookie с токеном
|
2025-05-20 00:00:24 +03:00
|
|
|
|
response.delete_cookie(
|
|
|
|
|
|
key=SESSION_COOKIE_NAME,
|
|
|
|
|
|
secure=SESSION_COOKIE_SECURE,
|
|
|
|
|
|
httponly=SESSION_COOKIE_HTTPONLY,
|
2025-09-28 12:22:37 +03:00
|
|
|
|
samesite=SESSION_COOKIE_SAMESITE if SESSION_COOKIE_SAMESITE in ["strict", "lax", "none"] else "none",
|
2025-05-20 00:00:24 +03:00
|
|
|
|
)
|
2025-05-16 09:23:48 +03:00
|
|
|
|
logger.info("[auth] logout: Cookie успешно удалена")
|
|
|
|
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-06-02 02:56:11 +03:00
|
|
|
|
async def refresh_token(request: Request) -> JSONResponse:
|
2025-05-16 09:23:48 +03:00
|
|
|
|
"""
|
|
|
|
|
|
Обновление токена аутентификации.
|
2025-05-29 12:37:39 +03:00
|
|
|
|
|
2025-05-20 00:00:24 +03:00
|
|
|
|
Поддерживает получение токена из:
|
|
|
|
|
|
1. HTTP-only cookie
|
|
|
|
|
|
2. Заголовка Authorization
|
2025-05-29 12:37:39 +03:00
|
|
|
|
|
2025-05-20 00:00:24 +03:00
|
|
|
|
Возвращает новый токен как в HTTP-only cookie, так и в теле ответа.
|
2025-05-16 09:23:48 +03:00
|
|
|
|
"""
|
[0.9.7] - 2025-08-18
### 🔄 Изменения
- **SQLAlchemy KeyError** - исправление ошибки `KeyError: Reaction` при инициализации
- **Исправлена ошибка SQLAlchemy**: Устранена проблема `InvalidRequestError: When initializing mapper Mapper[Shout(shout)], expression Reaction failed to locate a name (Reaction)`
### 🧪 Тестирование
- **Исправление тестов** - адаптация к новой структуре моделей
- **RBAC инициализация** - добавление `rbac.initialize_rbac()` в `conftest.py`
- **Создан тест для getSession**: Добавлен комплексный тест `test_getSession_cookies.py` с проверкой всех сценариев
- **Покрытие edge cases**: Тесты проверяют работу с валидными/невалидными токенами, отсутствующими пользователями
- **Мокирование зависимостей**: Использование unittest.mock для изоляции тестируемого кода
### 🔧 Рефакторинг
- **Упрощена архитектура**: Убраны сложные конструкции с отложенными импортами, заменены на чистую архитектуру
- **Перемещение моделей** - `Author` и связанные модели перенесены в `orm/author.py`: Вынесены базовые модели пользователей (`Author`, `AuthorFollower`, `AuthorBookmark`, `AuthorRating`) из `orm.author` в отдельный модуль
- **Устранены циклические импорты**: Разорван цикл между `auth.core` → `orm.community` → `orm.author` через реструктуризацию архитектуры
- **Создан модуль `utils/password.py`**: Класс `Password` вынесен в utils для избежания циклических зависимостей
- **Оптимизированы импорты моделей**: Убран прямой импорт `Shout` из `orm/community.py`, заменен на строковые ссылки
### 🔧 Авторизация с cookies
- **getSession теперь работает с cookies**: Мутация `getSession` теперь может получать токен из httpOnly cookies даже без заголовка Authorization
- **Убрано требование авторизации**: `getSession` больше не требует декоратор `@login_required`, работает автономно
- **Поддержка dual-авторизации**: Токен может быть получен как из заголовка Authorization, так и из cookie `session_token`
- **Автоматическая установка cookies**: Middleware автоматически устанавливает httpOnly cookies при успешном `getSession`
- **Обновлена GraphQL схема**: `SessionInfo` теперь содержит поля `success`, `error` и опциональные `token`, `author`
- **Единообразная обработка токенов**: Все модули теперь используют централизованные функции для работы с токенами
- **Улучшена обработка ошибок**: Добавлена детальная валидация токенов и пользователей в `getSession`
- **Логирование операций**: Добавлены подробные логи для отслеживания процесса авторизации
### 📝 Документация
- **Обновлена схема GraphQL**: `SessionInfo` тип теперь соответствует новому формату ответа
- Обновлена документация RBAC
- Обновлена документация авторизации с cookies
2025-08-18 14:25:25 +03:00
|
|
|
|
token = await extract_token_from_request(request)
|
2025-05-16 09:23:48 +03:00
|
|
|
|
|
|
|
|
|
|
if not token:
|
2025-05-20 00:00:24 +03:00
|
|
|
|
logger.warning("[auth] refresh_token: Токен не найден в запросе")
|
2025-05-16 09:23:48 +03:00
|
|
|
|
return JSONResponse({"success": False, "error": "Токен не найден"}, status_code=401)
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
# Получаем информацию о пользователе из токена
|
2025-06-02 02:56:11 +03:00
|
|
|
|
user_id, _, _ = await verify_internal_auth(token)
|
2025-05-16 09:23:48 +03:00
|
|
|
|
if not user_id:
|
2025-05-20 00:00:24 +03:00
|
|
|
|
logger.warning("[auth] refresh_token: Недействительный токен")
|
2025-05-16 09:23:48 +03:00
|
|
|
|
return JSONResponse({"success": False, "error": "Недействительный токен"}, status_code=401)
|
|
|
|
|
|
|
|
|
|
|
|
# Получаем пользователя из базы данных
|
|
|
|
|
|
with local_session() as session:
|
2025-07-31 18:55:59 +03:00
|
|
|
|
author = session.query(Author).where(Author.id == user_id).first()
|
2025-05-16 09:23:48 +03:00
|
|
|
|
|
|
|
|
|
|
if not author:
|
2025-05-20 00:00:24 +03:00
|
|
|
|
logger.warning(f"[auth] refresh_token: Пользователь с ID {user_id} не найден")
|
2025-05-16 09:23:48 +03:00
|
|
|
|
return JSONResponse({"success": False, "error": "Пользователь не найден"}, status_code=404)
|
|
|
|
|
|
|
|
|
|
|
|
# Обновляем сессию (создаем новую и отзываем старую)
|
2025-06-02 02:56:11 +03:00
|
|
|
|
device_info = {
|
|
|
|
|
|
"ip": request.client.host if request.client else "unknown",
|
|
|
|
|
|
"user_agent": request.headers.get("user-agent"),
|
|
|
|
|
|
}
|
2025-06-02 21:50:58 +03:00
|
|
|
|
new_token = await TokenStorage.refresh_session(user_id, token, device_info)
|
2025-05-16 09:23:48 +03:00
|
|
|
|
|
|
|
|
|
|
if not new_token:
|
2025-05-20 00:00:24 +03:00
|
|
|
|
logger.error(f"[auth] refresh_token: Не удалось обновить токен для пользователя {user_id}")
|
2025-05-29 12:37:39 +03:00
|
|
|
|
return JSONResponse({"success": False, "error": "Не удалось обновить токен"}, status_code=500)
|
2025-05-16 09:23:48 +03:00
|
|
|
|
|
[0.9.7] - 2025-08-18
### 🔄 Изменения
- **SQLAlchemy KeyError** - исправление ошибки `KeyError: Reaction` при инициализации
- **Исправлена ошибка SQLAlchemy**: Устранена проблема `InvalidRequestError: When initializing mapper Mapper[Shout(shout)], expression Reaction failed to locate a name (Reaction)`
### 🧪 Тестирование
- **Исправление тестов** - адаптация к новой структуре моделей
- **RBAC инициализация** - добавление `rbac.initialize_rbac()` в `conftest.py`
- **Создан тест для getSession**: Добавлен комплексный тест `test_getSession_cookies.py` с проверкой всех сценариев
- **Покрытие edge cases**: Тесты проверяют работу с валидными/невалидными токенами, отсутствующими пользователями
- **Мокирование зависимостей**: Использование unittest.mock для изоляции тестируемого кода
### 🔧 Рефакторинг
- **Упрощена архитектура**: Убраны сложные конструкции с отложенными импортами, заменены на чистую архитектуру
- **Перемещение моделей** - `Author` и связанные модели перенесены в `orm/author.py`: Вынесены базовые модели пользователей (`Author`, `AuthorFollower`, `AuthorBookmark`, `AuthorRating`) из `orm.author` в отдельный модуль
- **Устранены циклические импорты**: Разорван цикл между `auth.core` → `orm.community` → `orm.author` через реструктуризацию архитектуры
- **Создан модуль `utils/password.py`**: Класс `Password` вынесен в utils для избежания циклических зависимостей
- **Оптимизированы импорты моделей**: Убран прямой импорт `Shout` из `orm/community.py`, заменен на строковые ссылки
### 🔧 Авторизация с cookies
- **getSession теперь работает с cookies**: Мутация `getSession` теперь может получать токен из httpOnly cookies даже без заголовка Authorization
- **Убрано требование авторизации**: `getSession` больше не требует декоратор `@login_required`, работает автономно
- **Поддержка dual-авторизации**: Токен может быть получен как из заголовка Authorization, так и из cookie `session_token`
- **Автоматическая установка cookies**: Middleware автоматически устанавливает httpOnly cookies при успешном `getSession`
- **Обновлена GraphQL схема**: `SessionInfo` теперь содержит поля `success`, `error` и опциональные `token`, `author`
- **Единообразная обработка токенов**: Все модули теперь используют централизованные функции для работы с токенами
- **Улучшена обработка ошибок**: Добавлена детальная валидация токенов и пользователей в `getSession`
- **Логирование операций**: Добавлены подробные логи для отслеживания процесса авторизации
### 📝 Документация
- **Обновлена схема GraphQL**: `SessionInfo` тип теперь соответствует новому формату ответа
- Обновлена документация RBAC
- Обновлена документация авторизации с cookies
2025-08-18 14:25:25 +03:00
|
|
|
|
source = "cookie" if token.startswith("Bearer ") else "header"
|
|
|
|
|
|
|
2025-05-16 09:23:48 +03:00
|
|
|
|
# Создаем ответ
|
|
|
|
|
|
response = JSONResponse(
|
|
|
|
|
|
{
|
|
|
|
|
|
"success": True,
|
2025-05-20 00:00:24 +03:00
|
|
|
|
# Возвращаем токен в теле ответа только если он был получен из заголовка
|
|
|
|
|
|
"token": new_token if source == "header" else None,
|
2025-05-16 09:23:48 +03:00
|
|
|
|
"author": {"id": author.id, "email": author.email, "name": author.name},
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-05-20 00:00:24 +03:00
|
|
|
|
# Всегда устанавливаем cookie с новым токеном
|
2025-05-16 09:23:48 +03:00
|
|
|
|
response.set_cookie(
|
|
|
|
|
|
key=SESSION_COOKIE_NAME,
|
|
|
|
|
|
value=new_token,
|
|
|
|
|
|
httponly=SESSION_COOKIE_HTTPONLY,
|
|
|
|
|
|
secure=SESSION_COOKIE_SECURE,
|
2025-09-28 12:22:37 +03:00
|
|
|
|
samesite=SESSION_COOKIE_SAMESITE if SESSION_COOKIE_SAMESITE in ["strict", "lax", "none"] else "none",
|
2025-05-16 09:23:48 +03:00
|
|
|
|
max_age=SESSION_COOKIE_MAX_AGE,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(f"[auth] refresh_token: Токен успешно обновлен для пользователя {user_id}")
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"[auth] refresh_token: Ошибка при обновлении токена: {e}")
|
|
|
|
|
|
return JSONResponse({"success": False, "error": str(e)}, status_code=401)
|