core/docs/oauth.md
Untone 952b294345
All checks were successful
Deploy on push / deploy (push) Successful in 6s
0.5.8-panel-upgrade-community-crud-fix
2025-06-30 21:25:26 +03:00

12 KiB
Raw Blame 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.

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 дней по умолчанию).

await OAuthTokenStorage.store_refresh_token(
    user_id=123,
    provider="google",
    refresh_token="1//04...",
    ttl=2592000  # 30 дней
)

get_access_token()

Получает действующий access token из Redis.

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).

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()

Удаляет все токены пользователя для провайдера.

await OAuthTokenStorage.delete_tokens(123, "google")

get_user_providers()

Получает список OAuth провайдеров для пользователя.

providers = await OAuthTokenStorage.get_user_providers(123)
# ["google", "github"]

extend_token_ttl()

Продлевает срок действия токена.

# Продлить 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.

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

{
  "token": "ya29.a0AfH6SM...",
  "provider": "google",
  "user_id": 123,
  "created_at": 1640995200,
  "expires_in": 3600,
  "scope": "profile email",
  "token_type": "Bearer"
}

Refresh Token Structure

{
  "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

# После успешной авторизации через 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

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

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

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 токены в базе данных, используйте этот скрипт для миграции:

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
                        )

        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

# Проверка использования памяти OAuth токенами
redis-cli --scan --pattern "oauth_*" | wc -l
redis-cli memory usage oauth_access:123:google

Cleanup Statistics

# Периодическая очистка и логирование (опционально)
async def oauth_cleanup_job():
    cleaned = await OAuthTokenStorage.cleanup_expired_tokens()
    logger.info(f"OAuth cleanup completed, {cleaned} tokens processed")