101 lines
3.3 KiB
Python
101 lines
3.3 KiB
Python
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)
|