register logic

This commit is contained in:
2025-05-18 13:37:27 +03:00
parent be46e09aeb
commit 695c29ab62
19 changed files with 498 additions and 36 deletions

View File

View File

View File

@@ -0,0 +1,35 @@
from rest_framework import status
from rest_framework.viewsets import ViewSet
from rest_framework.permissions import IsAuthenticated
from rest_framework.decorators import action
from rest_framework.response import Response
from api.auth.serializers import UserResponseSerializer
from api.models import UserProfile
from api.utils.decorators import handle_exceptions
class UserDataView(ViewSet):
permission_classes = [IsAuthenticated]
def initial(self, request, *args, **kwargs):
try:
super().initial(request, *args, **kwargs)
except Exception as e:
print(f"Authentication error: {e}")
raise
@action(detail=False, methods=['get'])
@handle_exceptions
def user_data(self, request):
user = request.user
try:
user_data = UserResponseSerializer(user).data
return Response(user_data, status=status.HTTP_200_OK)
except UserProfile.DoesNotExist:
return Response(
{"error": "User profile not found"},
status=status.HTTP_404_NOT_FOUND
)

View File

@@ -5,7 +5,7 @@ from django.db.utils import IntegrityError
from api.models import UserProfile
class UserResponceSerializer(serializers.Serializer):
class UserResponseSerializer(serializers.Serializer):
id = serializers.IntegerField()
email = serializers.EmailField()
name = serializers.CharField(source='first_name')
@@ -24,16 +24,17 @@ class ClientRegisterSerializer(serializers.ModelSerializer):
phone_number = serializers.CharField(max_length=13)
privacy_accepted = serializers.BooleanField()
email = serializers.EmailField(required=True)
username = serializers.CharField(source='first_name')
name = serializers.CharField(required=True)
surname = serializers.CharField(required=True)
uuid = serializers.SerializerMethodField()
class Meta:
model = User
fields = ['id', 'uuid', 'username', 'email', 'password', 'phone_number', 'privacy_accepted']
fields = ['id', 'uuid', 'name', 'surname', 'email', 'password', 'phone_number', 'privacy_accepted']
extra_kwargs = {
'password' : {'write_only':True},
'email' : {'required' : True, 'unique': True},
'phone_number' : {'required' : True, 'unique': True},
'password': {'write_only': True},
'email': {'required': True},
'phone_number': {'required': True},
}
def validate_phone_number(self, value):
@@ -44,18 +45,21 @@ class ClientRegisterSerializer(serializers.ModelSerializer):
def validate_privacy_accepted(self, value):
if not value:
raise serializers.ValidationError("Необходимо принять условия политики конфиденциальности")
return value
def create(self, validated_data):
privacy_accepted = validated_data.pop('privacy_accepted')
phone_number = validated_data.pop('phone_number')
name = validated_data.pop('first_name')
name = validated_data.pop('name')
surname = validated_data.pop('surname')
try:
user = User.objects.create_user(
username=validated_data['name'],
username=validated_data['email'], # используем email как username
email=validated_data['email'],
password=validated_data['password'],
first_name = name,
first_name=name,
last_name=surname
)
UserProfile.objects.create(

View File

@@ -1,6 +1,171 @@
from rest_framework import serializers
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework_simplejwt.tokens import RefreshToken
import traceback
from django.contrib.auth.models import User
from django.conf import settings
from django.db.utils import IntegrityError
from django.contrib.auth.hashers import make_password
from django.db import IntegrityError, transaction
from .serializers import ClientRegisterSerializer, UserResponseSerializer
from api.utils.cookiesSet import AuthBaseViewSet
from datetime import datetime
class RegisterViewSet(AuthBaseViewSet):
"""Регистрация клиента"""
@action(detail=False, methods=['post'], url_path="clients")
def register_client(self, request):
serializer = ClientRegisterSerializer(data=request.data)
if serializer.is_valid():
try:
with transaction.atomic():
user = serializer.save()
refresh = RefreshToken.for_user(user)
user_data = UserResponseSerializer(user).data
response = Response(
{
"access": str(refresh.access_token),
"refresh": str(refresh),
"user": user_data
},
status=status.HTTP_201_CREATED
)
# используем метод из базового класса вместо прямой установки куки
return self._set_auth_cookies(response, refresh)
except IntegrityError as e:
return Response(
{"error": "Пользователь с таким email уже существует"},
status=status.HTTP_400_BAD_REQUEST
)
except Exception as e:
return Response(
{"error": str(e)},
status=status.HTTP_400_BAD_REQUEST
)
# ошибка валидации
return Response(
{
"error": "Ошибка валидации",
"details": serializer.errors
},
status=status.HTTP_400_BAD_REQUEST
)
class LoginViewSet(AuthBaseViewSet):
"""Логин для клиента"""
@action(detail=False, methods=['post'], url_path="clients")
def login_client(self, request):
try:
email = request.data.get("email")
password = request.data.get("password")
if not email or not password:
return Response(
{"error": "Email и пароль обязательны"},
status=status.HTTP_400_BAD_REQUEST
)
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
return Response(
{"error": "Пользователь не найден"},
status=status.HTTP_404_NOT_FOUND
)
if not user.check_password(password):
return Response(
{"error": "Неверный пароль"},
status=status.HTTP_403_FORBIDDEN
)
refresh = RefreshToken.for_user(user)
user_data = UserResponseSerializer(user).data
user_data["userType"] = "client"
response = Response({
"message": "Успешная авторизация",
"access": str(refresh.access_token),
"refresh": str(refresh),
"user": user_data
}, status=status.HTTP_200_OK)
# аналогично используем метод из базового класса
return self._set_auth_cookies(response, refresh)
except Exception as e:
return Response(
{"error": "Ошибка авторизации"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
class LogoutView(APIView):
"""Логаут"""
def post(self, request):
response = Response({'message': 'Logged out'}, status=status.HTTP_200_OK)
# чистим куки и sessionID
response.delete_cookie('access_token')
response.delete_cookie('refresh_token')
response.delete_cookie('sessionid')
return response
class RefreshTokenView(APIView):
def post(self, request):
try:
refresh_token = request.data.get('refresh')
if not refresh_token:
return Response(
{'error': 'Refresh token is required'},
status=status.HTTP_400_BAD_REQUEST
)
try:
token = RefreshToken(refresh_token)
# Сохраняем user_type при обновлении токена
if 'user_type' in token:
token.access_token['user_type'] = token['user_type']
# Добавляем точное время истечения токена
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'Invalid refresh token: {str(e)}'},
status=status.HTTP_400_BAD_REQUEST
)
except Exception as e:
return Response(
{'error': f'Token refresh failed: {str(e)}'},
status=status.HTTP_400_BAD_REQUEST
)

View File

@@ -17,4 +17,4 @@ class UserProfile(models.Model):
additionalDetails = models.TextField(null=True, blank=True, verbose_name="Дополнительные детали")
def __str__(self):
return {self.phone_number}
return str(self.phone_number)

View File

@@ -2,7 +2,22 @@ from django.urls import path
from api.main.views import FAQView, NewsView
from api.auth.views import (
RegisterViewSet,
LoginViewSet,
LogoutView,
RefreshTokenView)
from api.account.client.views import UserDataView
urlpatterns = [
path("v1/faq/", FAQView.as_view(), name='faqMain'),
path("v1/news/", NewsView.as_view(), name="newsmain")
path("v1/news/", NewsView.as_view(), name="newsmain"),
path("auth/refresh/", RefreshTokenView.as_view(), name="token-refresh"),
path("register/clients/", RegisterViewSet.as_view({'post': 'register_client'}), name="register-client"),
path("auth/login/clients/", LoginViewSet.as_view({'post': 'login_client'}), name="login-client"),
path("v1/logout", LogoutView.as_view(), name="logout"),
path ("v1/user/", UserDataView.as_view({'get': 'user_data'}), name="user"),
]

View File

@@ -0,0 +1,65 @@
from rest_framework.viewsets import ViewSet
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework import status
from datetime import datetime
from django.conf import settings
from rest_framework_simplejwt.tokens import RefreshToken
class AuthBaseViewSet(ViewSet):
"""Базовый класс для аутентификации с общими методами"""
def _set_auth_cookies(self, response, refresh):
"""Устанавливает куки для токенов аутентификации"""
response.set_cookie(
'access_token',
str(refresh.access_token),
httponly=True,
secure=True,
samesite='Lax',
max_age=300
)
response.set_cookie(
'refresh_token',
str(refresh),
httponly=True,
secure=True,
samesite='Lax',
max_age=86400
)
return response
@action(detail=False, methods=['post'], url_path="refresh")
def refresh_token(self, request):
try:
refresh_token = request.data.get('refresh')
if not refresh_token:
return Response(
{'error': 'Refresh token is required'},
status=status.HTTP_400_BAD_REQUEST
)
try:
token = RefreshToken(refresh_token)
response_data = {
'access': str(token.access_token),
'refresh': str(token),
'expires_at': datetime.timestamp(
datetime.now() + settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME']
)
}
return Response(response_data)
except Exception as e:
return Response(
{'error': f'Invalid refresh token: {str(e)}'},
status=status.HTTP_400_BAD_REQUEST
)
except Exception as e:
return Response(
{'error': f'Token refresh failed: {str(e)}'},
status=status.HTTP_400_BAD_REQUEST
)