diff --git a/Backend/user_service/user_service/user_app/models.py b/Backend/user_service/user_service/user_app/models.py index 09144bb..862bdc5 100644 --- a/Backend/user_service/user_service/user_app/models.py +++ b/Backend/user_service/user_service/user_app/models.py @@ -11,6 +11,12 @@ def user_directory_path(instance, filename): filename = f'{uuid.uuid4()}.{ext}' return os.path.join(str(instance.id), filename) +class ConfirmEmail(models.Model): + user_email = models.EmailField(unique=True, primary_key=True) + verify_status = models.BooleanField(default=False) + otp = models.CharField(null=True, blank=True) + otp_expiry_time = models.DateTimeField(blank=True, null=True) + class UserProfileModel(AbstractUser): """ User class to define the user model. @@ -22,6 +28,7 @@ class UserProfileModel(AbstractUser): Email: The email field is required for the user model. """ + email = models.OneToOneField(ConfirmEmail, related_name='user_profile', on_delete=models.CASCADE) avatar = models.ImageField(upload_to=user_directory_path, null=True, blank=True, default='default.jpg') friends = models.ManyToManyField("self", blank=True, symmetrical=True) online_status = models.BooleanField(default=False) diff --git a/Backend/user_service/user_service/user_app/serializers.py b/Backend/user_service/user_service/user_app/serializers.py index 4ffecb8..512bad9 100644 --- a/Backend/user_service/user_service/user_app/serializers.py +++ b/Backend/user_service/user_service/user_app/serializers.py @@ -2,7 +2,7 @@ from django.core.exceptions import ValidationError from rest_framework import serializers from rest_framework.validators import UniqueValidator -from .models import UserProfileModel, FriendRequest, GameRoom +from .models import UserProfileModel, FriendRequest, GameRoom, ConfirmEmail from .validators import CustomPasswordValidator class GameRoomSerializer(serializers.ModelSerializer): @@ -57,9 +57,6 @@ class UserSerializer(serializers.ModelSerializer): create: Method to create a new user. update: Method to update a user. """ - email = serializers.EmailField( - validators=[UniqueValidator(queryset=UserProfileModel.objects.all())] - ) avatar = serializers.ImageField(required=False) friends = serializers.PrimaryKeyRelatedField( many=True, queryset=UserProfileModel.objects.all(), required=False # required=False means that the field is not required @@ -83,7 +80,7 @@ class Meta: ### Password should be strong password, minimum 8 characters, at least one uppercase letter, one lowercase letter, one number and one special character - def create(self, validate_data) -> UserProfileModel: + def create(self, validated_data) -> UserProfileModel: """ Method to create a new user. @@ -91,23 +88,23 @@ def create(self, validate_data) -> UserProfileModel: The password is validated using CustomPasswordValidator. The password is hashed before saving the user object. Args: - validate_data: The data to validate. + validated_data: The data to validate. Returns: User: The user object. """ try: - validate_password(validate_data["password"]) + validate_password(validated_data["password"]) except ValidationError as err: raise serializers.ValidationError(detail=err.messages) from err - password = validate_data.pop("password", None) - instance = self.Meta.model(**validate_data) + password = validated_data.pop("password", None) + instance = self.Meta.model(**validated_data) if password is not None: instance.set_password(password) instance.save() return instance - def update(self, instance, validate_data) -> UserProfileModel: + def update(self, instance, validated_data) -> UserProfileModel: """ Method to update a user. @@ -116,7 +113,7 @@ def update(self, instance, validate_data) -> UserProfileModel: Args: instance: The user object. - validate_data: The data to validate. + validated_data: The data to validate. Returns: User: The updated user object. @@ -125,7 +122,7 @@ def update(self, instance, validate_data) -> UserProfileModel: serializers.ValidationError: If the password is the same as the current password. """ - for attr, value in validate_data.items(): + for attr, value in validated_data.items(): if attr == "password" and value is not None: if instance.check_password(value): raise serializers.ValidationError(detail="New password must be different from the current password.") @@ -141,3 +138,8 @@ def update(self, instance, validate_data) -> UserProfileModel: setattr(instance, attr, value) instance.save() return instance + +class ConfirmEmailSerializer(serializers.ModelSerializer): + class Meta: + model = ConfirmEmail + fields = '__all__' diff --git a/Backend/user_service/user_service/user_app/user_session_views.py b/Backend/user_service/user_service/user_app/user_session_views.py index b0df639..6646882 100644 --- a/Backend/user_service/user_service/user_app/user_session_views.py +++ b/Backend/user_service/user_service/user_app/user_session_views.py @@ -78,8 +78,6 @@ def login(self, request): if "error" in response_message: status_code = response_message.get("status_code") response_message = response.json() - else: - status_code = status.HTTP_200_OK else: response_message = {"detail": "User is Inactive"} status_code = status.HTTP_401_UNAUTHORIZED diff --git a/Backend/user_service/user_service/user_app/views.py b/Backend/user_service/user_service/user_app/views.py index 7c92982..d8be342 100644 --- a/Backend/user_service/user_service/user_app/views.py +++ b/Backend/user_service/user_service/user_app/views.py @@ -8,11 +8,11 @@ from rest_framework_simplejwt.authentication import JWTAuthentication from django.contrib.auth.hashers import check_password, make_password from rest_framework.exceptions import ValidationError -from .models import UserProfileModel, FriendRequest +from .models import UserProfileModel, FriendRequest, ConfirmEmail from rest_framework.parsers import MultiPartParser, FormParser from rest_framework.decorators import parser_classes -from django.utils.timezone import now -from .serializers import UserSerializer, FriendSerializer +from django.utils.timezone import now, timedelta +from .serializers import UserSerializer, FriendSerializer, ConfirmEmailSerializer from django.core.mail import send_mail from django.conf import settings from django.db.models import Q @@ -185,50 +185,50 @@ class RegisterViewSet(viewsets.ViewSet): """ permission_classes = [AllowAny] - def send_email(self, user, otp): + def send_email(self, email, otp): send_mail( 'Email verification code', f'Your verification code is: {otp}', settings.EMAIL_HOST_USER, - [user.email], + [email], fail_silently=False, ) def send_email_otp(self, request) -> Response: - username = request.data.get("username") - try: - user_obj = get_object_or_404(UserProfileModel, username = username) - response_message = {} - status_code = status.HTTP_200_OK - if user_obj is not None: + email = request.data.get("email") + response_message = {} + status_code = status.HTTP_200_OK + if email is not None: + try: + email_obj, create = ConfirmEmail.objects.get_or_create(user_email=email) otp = generate_secret() - user_obj.otp = make_password(str(otp)) - user_obj.otp_expiry_time = now() - user_obj.save() - self.send_email(user_obj, otp) + email_obj.otp = make_password(str(otp)) + email_obj.otp_expiry_time = now() + timedelta(minutes=1) + email_obj.save() + self.send_email(email_obj.user_email, otp) response_message = {"detail":"Email verification code sent to your email"} - else: - response_message = {"error": "email field required"} - status_code = status.HTTP_404_NOT_FOUND - except Exception as err: - response_message = {"error": str(err)} - status_code = status.HTTP_400_BAD_REQUEST + except Exception as err: + response_message = {"error": str(err)} + status_code = status.HTTP_400_BAD_REQUEST + else: + response_message = {"error": "email field required"} + status_code = status.HTTP_404_NOT_FOUND return Response(response_message, status=status_code) def verify_email_otp(self, request) -> Response: response_message = {} status_code = status.HTTP_200_OK - username = request.data.get('username') - if username is not None: - user = get_object_or_404(UserProfileModel, username = username) + email = request.data.get('email') + if email is not None: + email_obj = get_object_or_404(ConfirmEmail, user_email = email) otp = request.data.get('otp') - if otp is not None and user.otp is not None and user.otp_expiry_time is not None: - if check_password(str(otp), user.otp): - if user.otp_expiry_time > now(): + if otp is not None and email_obj.otp is not None and email_obj.otp_expiry_time is not None: + if check_password(str(otp), email_obj.otp): + if email_obj.otp_expiry_time > now(): response_message = {"detail":"Email verified"} - user.otp = None - user.otp_expiry_time = None - user.save() + email_obj.otp = None + email_obj.otp_expiry_time = None + email_obj.save() else: response_message = {"error":"otp expired"} status_code = status.HTTP_401_UNAUTHORIZED @@ -239,7 +239,7 @@ def verify_email_otp(self, request) -> Response: response_message = {'otp field required'} status_code = status.HTTP_400_BAD_REQUEST else: - response_message = {'error': 'user field required'} + response_message = {'error': 'email field required'} status_code = status.HTTP_400_BAD_REQUEST return Response(response_message, status=status_code) @@ -254,21 +254,32 @@ def create_user(self, request) -> Response: Returns: Response: The response object containing the user data. """ + response_message = {} + status_code = status.HTTP_201_CREATED try: - serializer = UserSerializer(data=request.data) - if serializer.is_valid(): - serializer.save() - return Response(serializer.data, status=status.HTTP_201_CREATED) - if serializer.errors: - data = serializer.errors - if "email" in data: - data["email"] = ["A user with that email already exists."] - return Response({"error":data}, status=status.HTTP_400_BAD_REQUEST) + email = request.data.get("email") + email_obj = get_object_or_404(ConfirmEmail, user_email = email) + if email_obj is not None: + serializer = UserSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + response_message = serializer.data + if serializer.errors: + data = serializer.errors + if "email" in data: + data["email"] = ["A user with that email already exists."] + response_message = {"error":data}, + status_code=status.HTTP_400_BAD_REQUEST + except Http404: + response_message = {"error": "You have not verified your email yet!"} + status_code = status.HTTP_401_UNAUTHORIZED except ValidationError as err: item_lists = [] for item in err.detail: item_lists.append(item) - return Response({'error': item_lists}, status=status.HTTP_400_BAD_REQUEST) + response_message = {'error': item_lists} + status_code=status.HTTP_400_BAD_REQUEST + return Response(response_message, status = status_code) class FriendsViewSet(viewsets.ViewSet): authentication_classes = [JWTAuthentication]