All checks were successful
Deploy on push / deploy (push) Successful in 5m47s
- **🔍 Comprehensive authentication documentation refactoring**: Полная переработка документации аутентификации
- Обновлена таблица содержания в README.md
- Исправлены архитектурные диаграммы - токены хранятся только в Redis
- Добавлены практические примеры кода для микросервисов
- Консолидирована OAuth документация
658 lines
16 KiB
Markdown
658 lines
16 KiB
Markdown
# 🔧 Auth API Reference
|
||
|
||
## 🎯 Обзор
|
||
|
||
Полный справочник по API системы аутентификации с примерами кода и использования.
|
||
|
||
## 📚 Token Managers
|
||
|
||
### SessionTokenManager
|
||
|
||
```python
|
||
from auth.tokens.sessions import SessionTokenManager
|
||
|
||
sessions = SessionTokenManager()
|
||
```
|
||
|
||
#### Методы
|
||
|
||
##### `create_session(user_id, auth_data=None, username=None, device_info=None)`
|
||
Создает новую сессию для пользователя.
|
||
|
||
**Параметры:**
|
||
- `user_id` (str): ID пользователя
|
||
- `auth_data` (dict, optional): Данные аутентификации
|
||
- `username` (str, optional): Имя пользователя
|
||
- `device_info` (dict, optional): Информация об устройстве
|
||
|
||
**Возвращает:** `str` - JWT токен
|
||
|
||
**Пример:**
|
||
```python
|
||
token = await sessions.create_session(
|
||
user_id="123",
|
||
username="john_doe",
|
||
device_info={"ip": "192.168.1.1", "user_agent": "Mozilla/5.0"}
|
||
)
|
||
```
|
||
|
||
##### `verify_session(token)`
|
||
Проверяет валидность JWT токена и Redis сессии.
|
||
|
||
**Параметры:**
|
||
- `token` (str): JWT токен
|
||
|
||
**Возвращает:** `dict | None` - Payload токена или None
|
||
|
||
**Пример:**
|
||
```python
|
||
payload = await sessions.verify_session(token)
|
||
if payload:
|
||
user_id = payload.get("user_id")
|
||
username = payload.get("username")
|
||
```
|
||
|
||
##### `validate_session_token(token)`
|
||
Валидирует токен сессии с дополнительными проверками.
|
||
|
||
**Параметры:**
|
||
- `token` (str): JWT токен
|
||
|
||
**Возвращает:** `tuple[bool, dict]` - (валидность, данные)
|
||
|
||
**Пример:**
|
||
```python
|
||
valid, data = await sessions.validate_session_token(token)
|
||
if valid:
|
||
print(f"Session valid for user: {data.get('user_id')}")
|
||
```
|
||
|
||
##### `get_session_data(token, user_id)`
|
||
Получает данные сессии из Redis.
|
||
|
||
**Параметры:**
|
||
- `token` (str): JWT токен
|
||
- `user_id` (str): ID пользователя
|
||
|
||
**Возвращает:** `dict | None` - Данные сессии
|
||
|
||
**Пример:**
|
||
```python
|
||
session_data = await sessions.get_session_data(token, user_id)
|
||
if session_data:
|
||
last_activity = session_data.get("last_activity")
|
||
```
|
||
|
||
##### `refresh_session(user_id, old_token, device_info=None)`
|
||
Обновляет сессию пользователя.
|
||
|
||
**Параметры:**
|
||
- `user_id` (str): ID пользователя
|
||
- `old_token` (str): Старый JWT токен
|
||
- `device_info` (dict, optional): Информация об устройстве
|
||
|
||
**Возвращает:** `str` - Новый JWT токен
|
||
|
||
**Пример:**
|
||
```python
|
||
new_token = await sessions.refresh_session(
|
||
user_id="123",
|
||
old_token=old_token,
|
||
device_info={"ip": "192.168.1.1"}
|
||
)
|
||
```
|
||
|
||
##### `revoke_session_token(token)`
|
||
Отзывает конкретный токен сессии.
|
||
|
||
**Параметры:**
|
||
- `token` (str): JWT токен
|
||
|
||
**Возвращает:** `bool` - Успешность операции
|
||
|
||
**Пример:**
|
||
```python
|
||
revoked = await sessions.revoke_session_token(token)
|
||
if revoked:
|
||
print("Session revoked successfully")
|
||
```
|
||
|
||
##### `get_user_sessions(user_id)`
|
||
Получает все активные сессии пользователя.
|
||
|
||
**Параметры:**
|
||
- `user_id` (str): ID пользователя
|
||
|
||
**Возвращает:** `list[dict]` - Список сессий
|
||
|
||
**Пример:**
|
||
```python
|
||
user_sessions = await sessions.get_user_sessions("123")
|
||
for session in user_sessions:
|
||
print(f"Token: {session['token'][:20]}...")
|
||
print(f"Last activity: {session['last_activity']}")
|
||
```
|
||
|
||
##### `revoke_user_sessions(user_id)`
|
||
Отзывает все сессии пользователя.
|
||
|
||
**Параметры:**
|
||
- `user_id` (str): ID пользователя
|
||
|
||
**Возвращает:** `int` - Количество отозванных сессий
|
||
|
||
**Пример:**
|
||
```python
|
||
revoked_count = await sessions.revoke_user_sessions("123")
|
||
print(f"Revoked {revoked_count} sessions")
|
||
```
|
||
|
||
### OAuthTokenManager
|
||
|
||
```python
|
||
from auth.tokens.oauth import OAuthTokenManager
|
||
|
||
oauth = OAuthTokenManager()
|
||
```
|
||
|
||
#### Методы
|
||
|
||
##### `store_oauth_tokens(user_id, provider, access_token, refresh_token=None, expires_in=3600, additional_data=None)`
|
||
Сохраняет OAuth токены в Redis.
|
||
|
||
**Параметры:**
|
||
- `user_id` (str): ID пользователя
|
||
- `provider` (str): OAuth провайдер (google, github, etc.)
|
||
- `access_token` (str): Access токен
|
||
- `refresh_token` (str, optional): Refresh токен
|
||
- `expires_in` (int): Время жизни в секундах
|
||
- `additional_data` (dict, optional): Дополнительные данные
|
||
|
||
**Пример:**
|
||
```python
|
||
await oauth.store_oauth_tokens(
|
||
user_id="123",
|
||
provider="google",
|
||
access_token="ya29.a0AfH6SM...",
|
||
refresh_token="1//04...",
|
||
expires_in=3600,
|
||
additional_data={"scope": "read write"}
|
||
)
|
||
```
|
||
|
||
##### `get_token(user_id, provider, token_type)`
|
||
Получает OAuth токен.
|
||
|
||
**Параметры:**
|
||
- `user_id` (str): ID пользователя
|
||
- `provider` (str): OAuth провайдер
|
||
- `token_type` (str): Тип токена ("oauth_access" или "oauth_refresh")
|
||
|
||
**Возвращает:** `dict | None` - Данные токена
|
||
|
||
**Пример:**
|
||
```python
|
||
access_data = await oauth.get_token("123", "google", "oauth_access")
|
||
if access_data:
|
||
token = access_data["token"]
|
||
expires_in = access_data.get("expires_in")
|
||
```
|
||
|
||
##### `revoke_oauth_tokens(user_id, provider)`
|
||
Отзывает OAuth токены провайдера.
|
||
|
||
**Параметры:**
|
||
- `user_id` (str): ID пользователя
|
||
- `provider` (str): OAuth провайдер
|
||
|
||
**Возвращает:** `bool` - Успешность операции
|
||
|
||
**Пример:**
|
||
```python
|
||
revoked = await oauth.revoke_oauth_tokens("123", "google")
|
||
if revoked:
|
||
print("OAuth tokens revoked")
|
||
```
|
||
|
||
### BatchTokenOperations
|
||
|
||
```python
|
||
from auth.tokens.batch import BatchTokenOperations
|
||
|
||
batch = BatchTokenOperations()
|
||
```
|
||
|
||
#### Методы
|
||
|
||
##### `batch_validate_tokens(tokens)`
|
||
Массовая валидация токенов.
|
||
|
||
**Параметры:**
|
||
- `tokens` (list[str]): Список JWT токенов
|
||
|
||
**Возвращает:** `dict[str, bool]` - Результаты валидации
|
||
|
||
**Пример:**
|
||
```python
|
||
tokens = ["token1", "token2", "token3"]
|
||
results = await batch.batch_validate_tokens(tokens)
|
||
# {"token1": True, "token2": False, "token3": True}
|
||
|
||
for token, is_valid in results.items():
|
||
print(f"Token {token[:10]}... is {'valid' if is_valid else 'invalid'}")
|
||
```
|
||
|
||
##### `batch_revoke_tokens(tokens)`
|
||
Массовый отзыв токенов.
|
||
|
||
**Параметры:**
|
||
- `tokens` (list[str]): Список JWT токенов
|
||
|
||
**Возвращает:** `int` - Количество отозванных токенов
|
||
|
||
**Пример:**
|
||
```python
|
||
revoked_count = await batch.batch_revoke_tokens(tokens)
|
||
print(f"Revoked {revoked_count} tokens")
|
||
```
|
||
|
||
##### `cleanup_expired_tokens()`
|
||
Очистка истекших токенов.
|
||
|
||
**Возвращает:** `int` - Количество очищенных токенов
|
||
|
||
**Пример:**
|
||
```python
|
||
cleaned_count = await batch.cleanup_expired_tokens()
|
||
print(f"Cleaned {cleaned_count} expired tokens")
|
||
```
|
||
|
||
### TokenMonitoring
|
||
|
||
```python
|
||
from auth.tokens.monitoring import TokenMonitoring
|
||
|
||
monitoring = TokenMonitoring()
|
||
```
|
||
|
||
#### Методы
|
||
|
||
##### `get_token_statistics()`
|
||
Получает статистику токенов.
|
||
|
||
**Возвращает:** `dict` - Статистика системы
|
||
|
||
**Пример:**
|
||
```python
|
||
stats = await monitoring.get_token_statistics()
|
||
print(f"Active sessions: {stats['session_tokens']}")
|
||
print(f"OAuth tokens: {stats['oauth_access_tokens']}")
|
||
print(f"Memory usage: {stats['memory_usage'] / 1024 / 1024:.2f} MB")
|
||
```
|
||
|
||
##### `health_check()`
|
||
Проверка здоровья системы токенов.
|
||
|
||
**Возвращает:** `dict` - Статус системы
|
||
|
||
**Пример:**
|
||
```python
|
||
health = await monitoring.health_check()
|
||
if health["status"] == "healthy":
|
||
print("Token system is healthy")
|
||
print(f"Redis connected: {health['redis_connected']}")
|
||
else:
|
||
print(f"System unhealthy: {health.get('error')}")
|
||
```
|
||
|
||
##### `optimize_memory_usage()`
|
||
Оптимизация использования памяти.
|
||
|
||
**Возвращает:** `dict` - Результаты оптимизации
|
||
|
||
**Пример:**
|
||
```python
|
||
results = await monitoring.optimize_memory_usage()
|
||
print(f"Cleaned expired: {results['cleaned_expired']}")
|
||
print(f"Memory freed: {results['memory_freed']} bytes")
|
||
```
|
||
|
||
## 🛠️ Utility Functions
|
||
|
||
### Auth Utils
|
||
|
||
```python
|
||
from auth.utils import (
|
||
extract_token_from_request,
|
||
get_auth_token,
|
||
get_auth_token_from_context,
|
||
get_safe_headers,
|
||
get_user_data_by_token
|
||
)
|
||
```
|
||
|
||
#### `extract_token_from_request(request)`
|
||
Извлекает токен из HTTP запроса.
|
||
|
||
**Параметры:**
|
||
- `request`: HTTP запрос (FastAPI, Starlette, etc.)
|
||
|
||
**Возвращает:** `str | None` - JWT токен или None
|
||
|
||
**Пример:**
|
||
```python
|
||
token = await extract_token_from_request(request)
|
||
if token:
|
||
print(f"Found token: {token[:20]}...")
|
||
```
|
||
|
||
#### `get_auth_token(request)`
|
||
Расширенное извлечение токена с логированием.
|
||
|
||
**Параметры:**
|
||
- `request`: HTTP запрос
|
||
|
||
**Возвращает:** `str | None` - JWT токен или None
|
||
|
||
**Пример:**
|
||
```python
|
||
token = await get_auth_token(request)
|
||
if token:
|
||
# Токен найден и залогирован
|
||
pass
|
||
```
|
||
|
||
#### `get_auth_token_from_context(info)`
|
||
Извлечение токена из GraphQL контекста.
|
||
|
||
**Параметры:**
|
||
- `info`: GraphQL Info объект
|
||
|
||
**Возвращает:** `str | None` - JWT токен или None
|
||
|
||
**Пример:**
|
||
```python
|
||
@auth_required
|
||
async def protected_resolver(info, **kwargs):
|
||
token = await get_auth_token_from_context(info)
|
||
# Используем токен для дополнительных проверок
|
||
```
|
||
|
||
#### `get_safe_headers(request)`
|
||
Безопасное получение заголовков запроса.
|
||
|
||
**Параметры:**
|
||
- `request`: HTTP запрос
|
||
|
||
**Возвращает:** `dict[str, str]` - Словарь заголовков
|
||
|
||
**Пример:**
|
||
```python
|
||
headers = get_safe_headers(request)
|
||
auth_header = headers.get("authorization", "")
|
||
user_agent = headers.get("user-agent", "")
|
||
```
|
||
|
||
#### `get_user_data_by_token(token)`
|
||
Получение данных пользователя по токену.
|
||
|
||
**Параметры:**
|
||
- `token` (str): JWT токен
|
||
|
||
**Возвращает:** `dict | None` - Данные пользователя
|
||
|
||
**Пример:**
|
||
```python
|
||
user_data = await get_user_data_by_token(token)
|
||
if user_data:
|
||
print(f"User: {user_data['username']}")
|
||
print(f"ID: {user_data['user_id']}")
|
||
```
|
||
|
||
## 🎭 Decorators
|
||
|
||
### GraphQL Decorators
|
||
|
||
```python
|
||
from auth.decorators import auth_required, permission_required
|
||
```
|
||
|
||
#### `@auth_required`
|
||
Требует авторизации для выполнения resolver'а.
|
||
|
||
**Пример:**
|
||
```python
|
||
@auth_required
|
||
async def get_user_profile(info, **kwargs):
|
||
"""Получение профиля пользователя"""
|
||
user = info.context.get('user')
|
||
return {
|
||
"id": user.id,
|
||
"username": user.username,
|
||
"email": user.email
|
||
}
|
||
```
|
||
|
||
#### `@permission_required(permission)`
|
||
Требует конкретного разрешения.
|
||
|
||
**Параметры:**
|
||
- `permission` (str): Название разрешения
|
||
|
||
**Пример:**
|
||
```python
|
||
@auth_required
|
||
@permission_required("shout:create")
|
||
async def create_shout(info, input_data):
|
||
"""Создание публикации"""
|
||
user = info.context.get('user')
|
||
|
||
shout = Shout(
|
||
title=input_data['title'],
|
||
content=input_data['content'],
|
||
author_id=user.id
|
||
)
|
||
|
||
return shout
|
||
```
|
||
|
||
## 🔧 Middleware
|
||
|
||
### AuthMiddleware
|
||
|
||
```python
|
||
from auth.middleware import AuthMiddleware
|
||
|
||
middleware = AuthMiddleware()
|
||
```
|
||
|
||
#### Методы
|
||
|
||
##### `authenticate_user(request)`
|
||
Аутентификация пользователя из запроса.
|
||
|
||
**Параметры:**
|
||
- `request`: HTTP запрос
|
||
|
||
**Возвращает:** `dict | None` - Данные пользователя
|
||
|
||
**Пример:**
|
||
```python
|
||
user_data = await middleware.authenticate_user(request)
|
||
if user_data:
|
||
request.user = user_data
|
||
```
|
||
|
||
##### `set_cookie(response, token)`
|
||
Установка httpOnly cookie с токеном.
|
||
|
||
**Параметры:**
|
||
- `response`: HTTP ответ
|
||
- `token` (str): JWT токен
|
||
|
||
**Пример:**
|
||
```python
|
||
await middleware.set_cookie(response, token)
|
||
```
|
||
|
||
##### `delete_cookie(response)`
|
||
Удаление cookie с токеном.
|
||
|
||
**Параметры:**
|
||
- `response`: HTTP ответ
|
||
|
||
**Пример:**
|
||
```python
|
||
await middleware.delete_cookie(response)
|
||
```
|
||
|
||
## 🔒 Error Handling
|
||
|
||
### Исключения
|
||
|
||
```python
|
||
from auth.exceptions import (
|
||
AuthenticationError,
|
||
InvalidTokenError,
|
||
TokenExpiredError,
|
||
OAuthError
|
||
)
|
||
```
|
||
|
||
#### `AuthenticationError`
|
||
Базовое исключение аутентификации.
|
||
|
||
**Пример:**
|
||
```python
|
||
try:
|
||
payload = await sessions.verify_session(token)
|
||
if not payload:
|
||
raise AuthenticationError("Invalid session token")
|
||
except AuthenticationError as e:
|
||
return {"error": str(e), "status": 401}
|
||
```
|
||
|
||
#### `InvalidTokenError`
|
||
Невалидный токен.
|
||
|
||
**Пример:**
|
||
```python
|
||
try:
|
||
valid, data = await sessions.validate_session_token(token)
|
||
if not valid:
|
||
raise InvalidTokenError("Token validation failed")
|
||
except InvalidTokenError as e:
|
||
return {"error": str(e), "status": 401}
|
||
```
|
||
|
||
#### `TokenExpiredError`
|
||
Истекший токен.
|
||
|
||
**Пример:**
|
||
```python
|
||
try:
|
||
# Проверка токена
|
||
pass
|
||
except TokenExpiredError as e:
|
||
return {"error": "Token expired", "status": 401}
|
||
```
|
||
|
||
## 📊 Response Formats
|
||
|
||
### Успешные ответы
|
||
|
||
```python
|
||
# Успешная аутентификация
|
||
{
|
||
"authenticated": True,
|
||
"user_id": "123",
|
||
"username": "john_doe",
|
||
"expires_at": 1640995200
|
||
}
|
||
|
||
# Статистика токенов
|
||
{
|
||
"session_tokens": 150,
|
||
"oauth_access_tokens": 25,
|
||
"oauth_refresh_tokens": 25,
|
||
"verification_tokens": 5,
|
||
"memory_usage": 1048576
|
||
}
|
||
|
||
# Health check
|
||
{
|
||
"status": "healthy",
|
||
"redis_connected": True,
|
||
"token_count": 205,
|
||
"timestamp": 1640995200
|
||
}
|
||
```
|
||
|
||
### Ошибки
|
||
|
||
```python
|
||
# Ошибка аутентификации
|
||
{
|
||
"authenticated": False,
|
||
"error": "Invalid or expired token",
|
||
"status": 401
|
||
}
|
||
|
||
# Ошибка системы
|
||
{
|
||
"status": "error",
|
||
"error": "Redis connection failed",
|
||
"timestamp": 1640995200
|
||
}
|
||
```
|
||
|
||
## 🧪 Testing Helpers
|
||
|
||
### Mock Utilities
|
||
|
||
```python
|
||
from unittest.mock import AsyncMock, patch
|
||
|
||
# Mock SessionTokenManager
|
||
@patch('auth.tokens.sessions.SessionTokenManager')
|
||
async def test_auth(mock_sessions):
|
||
mock_sessions.return_value.verify_session.return_value = {
|
||
"user_id": "123",
|
||
"username": "testuser"
|
||
}
|
||
|
||
# Ваш тест здесь
|
||
pass
|
||
|
||
# Mock Redis
|
||
@patch('storage.redis.redis')
|
||
async def test_redis_operations(mock_redis):
|
||
mock_redis.get.return_value = b'{"user_id": "123"}'
|
||
mock_redis.exists.return_value = True
|
||
|
||
# Ваш тест здесь
|
||
pass
|
||
```
|
||
|
||
### Test Fixtures
|
||
|
||
```python
|
||
import pytest
|
||
|
||
@pytest.fixture
|
||
async def auth_token():
|
||
"""Фикстура для создания тестового токена"""
|
||
sessions = SessionTokenManager()
|
||
return await sessions.create_session(
|
||
user_id="test_user",
|
||
username="testuser"
|
||
)
|
||
|
||
@pytest.fixture
|
||
async def authenticated_request(auth_token):
|
||
"""Фикстура для аутентифицированного запроса"""
|
||
mock_request = AsyncMock()
|
||
mock_request.headers = {"authorization": f"Bearer {auth_token}"}
|
||
return mock_request
|
||
```
|