refresh token route
This commit is contained in:
@@ -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
|
||||||
|
)
|
||||||
@@ -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/',
|
||||||
|
|||||||
@@ -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):
|
||||||
"""Выход из системы с очисткой всех токенов и куки"""
|
"""Выход из системы с очисткой всех токенов и куки"""
|
||||||
|
|||||||
@@ -90,7 +90,8 @@ SPECTACULAR_SETTINGS = {
|
|||||||
|
|
||||||
# сортировка тегов и операций
|
# сортировка тегов и операций
|
||||||
'TAGS': [
|
'TAGS': [
|
||||||
{'name': 'Аутентификация', 'description': 'Методы для работы с аутентификацией'},
|
{'name': 'Логаут', 'description': 'Метод для работы с логаутом'},
|
||||||
|
{'name': 'Логин', 'description': 'Методы для работы с логином'},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user