refresh token route

This commit is contained in:
Timofey
2025-08-29 15:13:15 +03:00
parent 9228a38511
commit 752afd372b
4 changed files with 118 additions and 5 deletions

View File

@@ -88,3 +88,27 @@ class LogoutResponseSerializer(serializers.Serializer):
help_text="Сообщение о успешном выходе", help_text="Сообщение о успешном выходе",
read_only=True read_only=True
) )
class RefreshTokenRequestSerializer(serializers.Serializer):
"""Сериализатор для запроса обновления токена"""
refresh = serializers.CharField(
help_text="Refresh token для обновления",
required=True
)
class RefreshTokenResponseSerializer(serializers.Serializer):
"""Сериализатор для ответа с обновленными токенами"""
access = serializers.CharField(
help_text="Новый JWT access token",
read_only=True
)
refresh = serializers.CharField(
help_text="Новый JWT refresh token",
read_only=True
)
expires_at = serializers.FloatField(
help_text="Timestamp времени истечения access token",
read_only=True
)

View File

@@ -1,5 +1,5 @@
from django.urls import path, include from django.urls import path, include
from .views import LoginViewSet, LogoutView from .views import LoginViewSet, LogoutView, RefreshTokenView
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
from drf_spectacular.views import ( from drf_spectacular.views import (
SpectacularAPIView, SpectacularAPIView,
@@ -13,6 +13,7 @@ router.register(r'', LoginViewSet, basename='auth')
urlpatterns = [ urlpatterns = [
path('', include(router.urls)), path('', include(router.urls)),
path('logout/', LogoutView.as_view(), name='auth-logout'), path('logout/', LogoutView.as_view(), name='auth-logout'),
path('refresh/', RefreshTokenView.as_view(), name='token-refresh'),
path('schema/', SpectacularAPIView.as_view(), name='schema'), path('schema/', SpectacularAPIView.as_view(), name='schema'),
path( path(
'docs/', 'docs/',

View File

@@ -1,4 +1,7 @@
from rest_framework import status from rest_framework import status
from datetime import datetime
import traceback
from django.conf import settings
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework_simplejwt.tokens import RefreshToken from rest_framework_simplejwt.tokens import RefreshToken
@@ -12,13 +15,16 @@ from .serializers import (
UserResponseSerializer, UserResponseSerializer,
LoginRequestSerializer, LoginRequestSerializer,
LoginResponseSerializer, LoginResponseSerializer,
LogoutResponseSerializer LogoutResponseSerializer,
RefreshTokenRequestSerializer,
RefreshTokenResponseSerializer
) )
from api.utils.cookies import AuthBaseViewSet from api.utils.cookies import AuthBaseViewSet
from api.types import User from api.types import User
@extend_schema(tags=['Логин'])
class LoginViewSet(AuthBaseViewSet): class LoginViewSet(AuthBaseViewSet):
"""ViewSet для авторизации пользователей""" """ViewSet для авторизации пользователей"""
serializer_class = LoginRequestSerializer serializer_class = LoginRequestSerializer
@@ -137,6 +143,88 @@ class LoginViewSet(AuthBaseViewSet):
status=status.HTTP_500_INTERNAL_SERVER_ERROR status=status.HTTP_500_INTERNAL_SERVER_ERROR
) )
@extend_schema(tags=['Логин'])
class RefreshTokenView(APIView):
"""View для обновления JWT токенов"""
serializer_class = RefreshTokenRequestSerializer
@extend_schema(
summary="Обновление токенов",
description="Эндпоинт для обновления JWT токенов. Принимает refresh token и возвращает новую пару токенов.",
request=RefreshTokenRequestSerializer,
responses={
200: OpenApiResponse(
response=RefreshTokenResponseSerializer,
description="Токены успешно обновлены",
examples=[
OpenApiExample(
'Успешное обновление',
value={
"access": "eyJ0eXAiOiJKV1QiLCJhbGc...",
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGc...",
"expires_at": 1679831642.0
}
)
]
),
400: OpenApiResponse(
description="Ошибка обновления токена",
response=OpenApiTypes.OBJECT,
examples=[
OpenApiExample(
'Отсутствует refresh token',
value={"error": "Refresh token is required"}
),
OpenApiExample(
'Невалидный refresh token',
value={"error": "Invalid refresh token: Token is invalid or expired"}
)
]
)
}
)
def post(self, request):
try:
refresh_token = request.data.get('refresh')
if not refresh_token:
return Response(
{'error': 'Требуется refresh token'},
status=status.HTTP_400_BAD_REQUEST
)
try:
token = RefreshToken(refresh_token)
# точное время истечения токена
expires_at = datetime.now() + settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME']
response_data = {
'access': str(token.access_token),
'refresh': str(token),
'expires_at': datetime.timestamp(expires_at)
}
return Response(response_data)
except Exception as e:
# логируем ошибки
print(f"Token refresh error: {str(e)}")
print(traceback.format_exc())
return Response(
{'error': f'Невалидный refresh token: {str(e)}'},
status=status.HTTP_400_BAD_REQUEST
)
except Exception as e:
return Response(
{'error': f'Ошибка обновления токена: {str(e)}'},
status=status.HTTP_400_BAD_REQUEST
)
@extend_schema(tags=['Логаут'])
class LogoutView(APIView): class LogoutView(APIView):
"""View для выхода из системы""" """View для выхода из системы"""
serializer_class = LogoutResponseSerializer serializer_class = LogoutResponseSerializer
@@ -155,8 +243,7 @@ class LogoutView(APIView):
) )
] ]
) )
}, }
tags=['Аутентификация']
) )
def post(self, request): def post(self, request):
"""Выход из системы с очисткой всех токенов и куки""" """Выход из системы с очисткой всех токенов и куки"""

View File

@@ -90,7 +90,8 @@ SPECTACULAR_SETTINGS = {
# сортировка тегов и операций # сортировка тегов и операций
'TAGS': [ 'TAGS': [
{'name': 'Аутентификация', 'description': 'Методы для работы с аутентификацией'}, {'name': 'Логаут', 'description': 'Метод для работы с логаутом'},
{'name': 'Логин', 'description': 'Методы для работы с логином'},
], ],
} }