Django: error trying to validate an email verification otp

Viewed 94

I'm sending an email to the user on register and getting the otp together with the email, I have a separate verify model that looks like below the error I'm facing is {"message": "This OTP is invalid"} which has something to do with peace of code in the verifyEmail view(down below)

class Verify(models.Model):
    email = models.EmailField(
        _("Email"), max_length=254, unique=True, default=None, blank=True, null=True)
    otp = IntegerRangeField(
        min_value=111111, max_value=999999, blank=True, null=True)
    created_at = models.DateTimeField(
        _("created at"), auto_now=False, auto_now_add=True, blank=False)
    expires_at = models.DateTimeField(null=True)

    def __str__(self) -> str:
        return self.email

how I'm sending the email

def send_opt_email(email, firstname, lastname):
    otp = random.randint(100000, 999999)
    subject = "Email verification"
    message = "Email verification"
    html_message = loader.render_to_string(
        "email_verify.html",
        {
            "firstname": firstname,
            "lastname": lastname,
            "otp": otp,
        },
    )
    email_from = settings.EMAIL_HOST_USER
    send_mail(
        subject,
        message,
        email_from,
        [email],
        fail_silently=True,
        html_message=html_message,
    )
    otp = Verify.objects.create(
        email=email,
        otp=otp,
        created_at=datetime.now(),
        expires_at=datetime.now() + timedelta(minutes=30),
    )
    otp.save()

this is the view I use to validate the otp

class Verify_Email(APIView):
    """
    Verify registered emails
    """

    def post(self, request):
        try:
            data = request.data
            serializer = VerifySerializerBase(data=data)
            if serializer.is_valid():
                email = serializer.data["email"]
                otp = serializer.data["otp"]
                verify = Verify.objects.get(email=email)
                user = User.objects.get(email=email)

                try:
                    if user.is_active:
                        return Response(
                            {"message": "This email has already been verified"},
                            status=status.HTTP_400_BAD_REQUEST,
                        )

                    elif verify:
                        print(verify.email)
                        print(verify.otp)
                        print(verify.created_at)
                        print(verify.expires_at)
                        # prints everything as expected
                        now = timezone.now()
                        if verify.otp != otp:
                            return Response(
                                {"message": "This OTP is invalid"},
                                status=status.HTTP_400_BAD_REQUEST,
                            )
                        elif verify.expires_at < now:
                            return Response(
                                {
                                    "message": "This OTP has expired, please request another one"
                                },
                                status=status.HTTP_400_BAD_REQUEST,
                            )
                        verify.delete()
                        user.is_active = True
                        user.save()
                        return Response(
                            {"message": "Email has been verified"},
                            status=status.HTTP_200_OK,
                        )
                except User.DoesNotExist:
                    return Response(
                        {"message": "User was not found"},
                        status=status.HTTP_404_NOT_FOUND,
                    )

                return Response(
                    {"message": "Something is wrong"},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            return Response(
                {"message": "Something is wrong"}, status=status.HTTP_400_BAD_REQUEST
            )

        except Exception as e:
            return Response(
                str(e),
                status=status.HTTP_404_NOT_FOUND,
                template_name=None,
                content_type=None,
            )

I don't think the Verify_Email view is a nice implementation and any improvements would be appreciated

2 Answers

Did you try type casting the variable? (i. e. if int(verify.otp) != int(otp):)

Also, are you 100% sure verify.otp == otp? I see you're printing verify.otp but not otp. What's both results?

You got what you were looking for in Sören Rifé's answer and I just cleaned up your code. (however you might not like it)

from django.shortcuts import get_object_or_404
from rest_framework.exceptions import ValidationError

class Verify_Email(APIView):
    def post(self, request):
        serializer = VerifySerializerBase(data=request.data)
        if not serializer.is_valid():
            raise ValidationError(
                "Something is wrong"
            )
        
        email = serializer.data['email']
        otp = serializer.data['otp']

        user = get_object_or_404(User, email=email)
        if user.is_active:
            raise ValidationError(
                "This email has already been verified",
            )

        verify = get_object_or_404(Verify, email=email)
        if int(verify.otp) != int(otp):
            raise ValidationError(
                "This OTP is invalid"
            )
        if verify.expired:
            raise ValidationError(
                "This OTP has expired, please request another one"
            )

        verify.delete()
        user.is_active = True
        user.save()
        return Response(
            {'message': "Email has been verified"},
            status=status.HTTP_200_OK
        )
from datetime import datetime

class Verify(models.Model):
    email = models.EmailField(
        _("Email"), max_length=254, unique=True, default=None, blank=True, null=True)
    otp = IntegerRangeField(
        min_value=111111, max_value=999999, blank=True, null=True)
    created_at = models.DateTimeField(
        _("created at"), auto_now_add=True)
    expires_at = models.DateTimeField(null=True)

    def __str__(self) -> str:
        return self.email

    @property
    def expired(self):
        return self.expires_at < datetime.now()
Related