core/docs/oauth.md

330 lines
12 KiB
Markdown
Raw Permalink Normal View History

# OAuth Token Management
## Overview
Система управления OAuth токенами с использованием Redis для безопасного и производительного хранения токенов доступа и обновления от различных провайдеров.
## Архитектура
### Redis Storage
OAuth токены хранятся в Redis с автоматическим истечением (TTL):
- `oauth_access:{user_id}:{provider}` - access tokens
- `oauth_refresh:{user_id}:{provider}` - refresh tokens
### Поддерживаемые провайдеры
- Google OAuth 2.0
- Facebook Login
- GitHub OAuth
## API Documentation
### OAuthTokenStorage Class
#### store_access_token()
Сохраняет access token в Redis с автоматическим TTL.
```python
await OAuthTokenStorage.store_access_token(
user_id=123,
provider="google",
access_token="ya29.a0AfH6SM...",
expires_in=3600,
additional_data={"scope": "profile email"}
)
```
#### store_refresh_token()
Сохраняет refresh token с длительным TTL (30 дней по умолчанию).
```python
await OAuthTokenStorage.store_refresh_token(
user_id=123,
provider="google",
refresh_token="1//04...",
ttl=2592000 # 30 дней
)
```
#### get_access_token()
Получает действующий access token из Redis.
```python
token_data = await OAuthTokenStorage.get_access_token(123, "google")
if token_data:
access_token = token_data["token"]
expires_in = token_data["expires_in"]
```
#### refresh_access_token()
Обновляет access token (и опционально refresh token).
```python
success = await OAuthTokenStorage.refresh_access_token(
user_id=123,
provider="google",
new_access_token="ya29.new_token...",
expires_in=3600,
new_refresh_token="1//04new..." # опционально
)
```
#### delete_tokens()
Удаляет все токены пользователя для провайдера.
```python
await OAuthTokenStorage.delete_tokens(123, "google")
```
#### get_user_providers()
Получает список OAuth провайдеров для пользователя.
```python
providers = await OAuthTokenStorage.get_user_providers(123)
# ["google", "github"]
```
#### extend_token_ttl()
Продлевает срок действия токена.
```python
# Продлить access token на 30 минут
success = await OAuthTokenStorage.extend_token_ttl(123, "google", "access", 1800)
# Продлить refresh token на 7 дней
success = await OAuthTokenStorage.extend_token_ttl(123, "google", "refresh", 604800)
```
#### get_token_info()
Получает подробную информацию о токенах включая TTL.
```python
info = await OAuthTokenStorage.get_token_info(123, "google")
# {
# "user_id": 123,
# "provider": "google",
# "access_token": {"exists": True, "ttl": 3245},
# "refresh_token": {"exists": True, "ttl": 2589600}
# }
```
## Data Structures
### Access Token Structure
```json
{
"token": "ya29.a0AfH6SM...",
"provider": "google",
"user_id": 123,
"created_at": 1640995200,
"expires_in": 3600,
"scope": "profile email",
"token_type": "Bearer"
}
```
### Refresh Token Structure
```json
{
"token": "1//04...",
"provider": "google",
"user_id": 123,
"created_at": 1640995200
}
```
## Security Considerations
### Token Expiration
- **Access tokens**: TTL основан на `expires_in` от провайдера (обычно 1 час)
- **Refresh tokens**: TTL 30 дней по умолчанию
- **Автоматическая очистка**: Redis автоматически удаляет истекшие токены
- **Внутренняя система истечения**: Использует SET + EXPIRE для точного контроля TTL
### Redis Expiration Benefits
- **Гибкость**: Можно изменять TTL существующих токенов через EXPIRE
- **Мониторинг**: Команда TTL показывает оставшееся время жизни токена
- **Расширение**: Возможность продления срока действия токенов без перезаписи
- **Атомарность**: Separate SET/EXPIRE operations для лучшего контроля
### Access Control
- Токены доступны только владельцу аккаунта
- Нет доступа к токенам через GraphQL API
- Токены не хранятся в основной базе данных
### Provider Isolation
- Токены разных провайдеров хранятся отдельно
- Удаление токенов одного провайдера не влияет на другие
- Поддержка множественных OAuth подключений
## Integration Examples
### OAuth Login Flow
```python
# После успешной авторизации через OAuth провайдера
async def handle_oauth_callback(user_id: int, provider: str, tokens: dict):
# Сохраняем токены в Redis
await OAuthTokenStorage.store_access_token(
user_id=user_id,
provider=provider,
access_token=tokens["access_token"],
expires_in=tokens.get("expires_in", 3600)
)
if "refresh_token" in tokens:
await OAuthTokenStorage.store_refresh_token(
user_id=user_id,
provider=provider,
refresh_token=tokens["refresh_token"]
)
```
### Token Refresh
```python
async def refresh_oauth_token(user_id: int, provider: str):
# Получаем refresh token
refresh_data = await OAuthTokenStorage.get_refresh_token(user_id, provider)
if not refresh_data:
return False
# Обмениваем refresh token на новый access token
new_tokens = await exchange_refresh_token(
provider, refresh_data["token"]
)
# Сохраняем новые токены
return await OAuthTokenStorage.refresh_access_token(
user_id=user_id,
provider=provider,
new_access_token=new_tokens["access_token"],
expires_in=new_tokens.get("expires_in"),
new_refresh_token=new_tokens.get("refresh_token")
)
```
### API Integration
```python
async def make_oauth_request(user_id: int, provider: str, endpoint: str):
# Получаем действующий access token
token_data = await OAuthTokenStorage.get_access_token(user_id, provider)
if not token_data:
# Токен отсутствует, требуется повторная авторизация
raise OAuthTokenMissing()
# Делаем запрос к API провайдера
headers = {"Authorization": f"Bearer {token_data['token']}"}
response = await httpx.get(endpoint, headers=headers)
if response.status_code == 401:
# Токен истек, пытаемся обновить
if await refresh_oauth_token(user_id, provider):
# Повторяем запрос с новым токеном
token_data = await OAuthTokenStorage.get_access_token(user_id, provider)
headers = {"Authorization": f"Bearer {token_data['token']}"}
response = await httpx.get(endpoint, headers=headers)
return response.json()
```
### TTL Monitoring and Management
```python
async def monitor_token_expiration(user_id: int, provider: str):
"""Мониторинг и управление сроком действия токенов"""
# Получаем информацию о токенах
info = await OAuthTokenStorage.get_token_info(user_id, provider)
# Проверяем access token
if info["access_token"]["exists"]:
ttl = info["access_token"]["ttl"]
if ttl < 300: # Меньше 5 минут
logger.warning(f"Access token expires soon: {ttl}s")
# Автоматически обновляем токен
await refresh_oauth_token(user_id, provider)
# Проверяем refresh token
if info["refresh_token"]["exists"]:
ttl = info["refresh_token"]["ttl"]
if ttl < 86400: # Меньше 1 дня
logger.warning(f"Refresh token expires soon: {ttl}s")
# Уведомляем пользователя о необходимости повторной авторизации
async def extend_session_if_active(user_id: int, provider: str):
"""Продлевает сессию для активных пользователей"""
# Проверяем активность пользователя
if await is_user_active(user_id):
# Продлеваем access token на 1 час
success = await OAuthTokenStorage.extend_token_ttl(
user_id, provider, "access", 3600
)
if success:
logger.info(f"Extended access token for active user {user_id}")
```
## Migration from Database
Если у вас уже есть OAuth токены в базе данных, используйте этот скрипт для миграции:
```python
async def migrate_oauth_tokens():
"""Миграция OAuth токенов из БД в Redis"""
with local_session() as session:
# Предполагая, что токены хранились в таблице authors
authors = session.query(Author).filter(
or_(
Author.provider_access_token.is_not(None),
Author.provider_refresh_token.is_not(None)
)
).all()
for author in authors:
# Получаем провайдер из oauth вместо старого поля oauth
if author.oauth:
for provider in author.oauth.keys():
if author.provider_access_token:
await OAuthTokenStorage.store_access_token(
user_id=author.id,
provider=provider,
access_token=author.provider_access_token
)
if author.provider_refresh_token:
await OAuthTokenStorage.store_refresh_token(
user_id=author.id,
provider=provider,
refresh_token=author.provider_refresh_token
)
2025-06-30 18:25:26 +00:00
print(f"Migrated OAuth tokens for {len(authors)} authors")
```
## Performance Benefits
### Redis Advantages
- **Скорость**: Доступ к токенам за микросекунды
- **Масштабируемость**: Не нагружает основную БД
- **Автоматическая очистка**: TTL убирает истекшие токены
- **Память**: Эффективное использование памяти Redis
### Reduced Database Load
- OAuth токены больше не записываются в основную БД
- Уменьшено количество записей в таблице authors
- Faster user queries без JOIN к токенам
## Monitoring and Maintenance
### Redis Memory Usage
```bash
# Проверка использования памяти OAuth токенами
redis-cli --scan --pattern "oauth_*" | wc -l
redis-cli memory usage oauth_access:123:google
```
### Cleanup Statistics
```python
# Периодическая очистка и логирование (опционально)
async def oauth_cleanup_job():
cleaned = await OAuthTokenStorage.cleanup_expired_tokens()
logger.info(f"OAuth cleanup completed, {cleaned} tokens processed")
```