from __future__ import annotations import logging import time from drf_spectacular.utils import ( OpenApiExample, OpenApiResponse, extend_schema, ) from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView from ..serializers import ( PhoneOrEmailTokenObtainPairSerializer, SessionTokenRefreshSerializer, TokenPairResponseSerializer, TokenRefreshRequestSerializer, TokenRefreshResponseSerializer, ) from ..services import ( extract_client_ip, ) logger = logging.getLogger(__name__) @extend_schema( tags=["Auth"], summary="Получить JWT access и refresh токены", description=( "Принимает LDAP-логин, email или телефон вместе с паролем. " "Рекомендуемое поле идентификатора — login; старые поля email, " "phone и phone_number поддерживаются для обратной совместимости." ), request=PhoneOrEmailTokenObtainPairSerializer, responses={ 200: TokenPairResponseSerializer, 401: OpenApiResponse( description=( "Неверные учетные данные или email пользователя не подтвержден." ), ), }, examples=[ OpenApiExample( "Вход по LDAP-логину", value={"login": "ivanov", "password": "Str0ngPass!"}, request_only=True, ), OpenApiExample( "Вход по email", value={"login": "user@example.com", "password": "Str0ngPass!"}, request_only=True, ), OpenApiExample( "Вход по телефону", value={"login": "+79990000001", "password": "Str0ngPass!"}, request_only=True, ), ], ) class PhoneOrEmailTokenObtainPairView(TokenObtainPairView): serializer_class = PhoneOrEmailTokenObtainPairSerializer throttle_scope = "auth_login" @extend_schema( tags=["Auth"], summary="Обновить JWT access token", description="Принимает refresh token и возвращает новый access token.", request=TokenRefreshRequestSerializer, responses={ 200: TokenRefreshResponseSerializer, 401: OpenApiResponse( description="Refresh token недействителен, истек или сессия отозвана." ), }, ) class JWTTokenRefreshView(TokenRefreshView): serializer_class = SessionTokenRefreshSerializer throttle_scope = "auth_refresh" def initial(self, request, *args, **kwargs): self._refresh_started_at = time.monotonic() return super().initial(request, *args, **kwargs) def finalize_response(self, request, response, *args, **kwargs): duration_ms = round( (time.monotonic() - getattr(self, "_refresh_started_at", time.monotonic())) * 1000, 1, ) log = logger.info if response.status_code < 400 else logger.warning log( "JWT refresh completed status=%s duration_ms=%s ip=%s", response.status_code, duration_ms, extract_client_ip(request), ) return super().finalize_response(request, response, *args, **kwargs)