refresh token route
This commit is contained in:
@@ -88,3 +88,27 @@ class LogoutResponseSerializer(serializers.Serializer):
|
||||
help_text="Сообщение о успешном выходе",
|
||||
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
|
||||
)
|
||||
@@ -1,5 +1,5 @@
|
||||
from django.urls import path, include
|
||||
from .views import LoginViewSet, LogoutView
|
||||
from .views import LoginViewSet, LogoutView, RefreshTokenView
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from drf_spectacular.views import (
|
||||
SpectacularAPIView,
|
||||
@@ -13,6 +13,7 @@ router.register(r'', LoginViewSet, basename='auth')
|
||||
urlpatterns = [
|
||||
path('', include(router.urls)),
|
||||
path('logout/', LogoutView.as_view(), name='auth-logout'),
|
||||
path('refresh/', RefreshTokenView.as_view(), name='token-refresh'),
|
||||
path('schema/', SpectacularAPIView.as_view(), name='schema'),
|
||||
path(
|
||||
'docs/',
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
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.response import Response
|
||||
from rest_framework_simplejwt.tokens import RefreshToken
|
||||
@@ -12,13 +15,16 @@ from .serializers import (
|
||||
UserResponseSerializer,
|
||||
LoginRequestSerializer,
|
||||
LoginResponseSerializer,
|
||||
LogoutResponseSerializer
|
||||
LogoutResponseSerializer,
|
||||
RefreshTokenRequestSerializer,
|
||||
RefreshTokenResponseSerializer
|
||||
)
|
||||
|
||||
from api.utils.cookies import AuthBaseViewSet
|
||||
from api.types import User
|
||||
|
||||
|
||||
@extend_schema(tags=['Логин'])
|
||||
class LoginViewSet(AuthBaseViewSet):
|
||||
"""ViewSet для авторизации пользователей"""
|
||||
serializer_class = LoginRequestSerializer
|
||||
@@ -137,6 +143,88 @@ class LoginViewSet(AuthBaseViewSet):
|
||||
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):
|
||||
"""View для выхода из системы"""
|
||||
serializer_class = LogoutResponseSerializer
|
||||
@@ -155,8 +243,7 @@ class LogoutView(APIView):
|
||||
)
|
||||
]
|
||||
)
|
||||
},
|
||||
tags=['Аутентификация']
|
||||
}
|
||||
)
|
||||
def post(self, request):
|
||||
"""Выход из системы с очисткой всех токенов и куки"""
|
||||
|
||||
@@ -90,7 +90,8 @@ SPECTACULAR_SETTINGS = {
|
||||
|
||||
# сортировка тегов и операций
|
||||
'TAGS': [
|
||||
{'name': 'Аутентификация', 'description': 'Методы для работы с аутентификацией'},
|
||||
{'name': 'Логаут', 'description': 'Метод для работы с логаутом'},
|
||||
{'name': 'Логин', 'description': 'Методы для работы с логином'},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user