Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OTP Verification in Django Rest Framework

I am trying to make a django app in which I want to create a opt verification but I am confused what is the right approach to do it. Here's what I have done so far:

Models.py

class User(AbstractUser):
    is_shipper = models.BooleanField(default=False)
    is_ftlsupp = models.BooleanField(default=False)
    is_ptlsupp = models.BooleanField(default=False)
    otp = models.IntegerField(default=1620122)
    verified = models.BooleanField(default=False)

Serializers.py

class UserSerializer(serializers.ModelSerializer):
    password = serializers.CharField(write_only=True)

    class Meta:
        model = User
        fields = "__all__"
        read_only_fields = ('id', 'verified')

    def create(self, validated_data):
        user = super(UserSerializer, self).create(validated_data)
        user.set_password(validated_data['password'])

        def random_with_N_digits(n):
            range_start = 10**(n-1)
            range_end = (10**n)-1
            return randint(range_start, range_end)

        otp = random_with_N_digits(6)
        user.otp = otp
        user.save()

        subject = 'Please Confirm Your Account'
        message = 'Your 6 Digit Verification Pin: {}'.format(otp)
        email_from = '*****'
        recipient_list = [str(user.email), ]
        send_mail(subject, message, email_from, recipient_list)
        return user

How can I use this otp to verify the user? My approach is that if the user is created and he tries to login then obviously he is unverified as verified = models.BooleanField(default=False) so he'd be shown a pop up to enter the otp he received on his mail and if the otp matches he can proceed and log-in

Views.py

To verify otp

class verifyOTPView(APIView):

    def post(self, request):
        username = request.data["username"]
        otp = int(request.data["otp"])
        user = User.objects.get(username=username)
        if int(user.otp)==otp:
            user.verified = True
            #user.otp.delete()  #?? How to handle the otp, Should I set it to null??
            user.save()
            return Response("Verification Successful")
        else:
            raise PermissionDenied("OTP Verification failed")

Please suggest how should I proceed and would it be wise to use the same otp field for Resetting the password?

like image 749
Rahul Sharma Avatar asked Jan 01 '23 02:01

Rahul Sharma


2 Answers

First, create a PhoneOTP model

class PhoneOTP(models.Model):
     username = models.CharField(max_length=254, unique=True, blank=True, default=False)
     phone_regex = RegexValidator( regex = r'^\+?1?\d{9,14}$', message = "Phone number must be entered in the form of +919999999999.")
     name = models.CharField(max_length=254, blank=True, null=True)
     phone = models.CharField(validators = [phone_regex], max_length=17)
     otp = models.CharField(max_length=9, blank=True, null=True)
     count = models.IntegerField(default=0, help_text = 'Number of opt_sent')
     validated = models.BooleanField(default=False, help_text= 'if it is true, that means user have validate opt correctly in seconds')

     def __str__(self):
         return str(self.phone) + ' is sent ' + str(self.otp)

Then create an OTP generate view and verify view

class ValidatePhoneSendOTP(APIView):
    permission_classes = (permissions.AllowAny, )
    def post(self, request, *args, **kwargs):
        name = request.data.get('name' , False)
        phone_number = request.data.get('phone')
        if phone_number:
            phone  = str(phone_number)
            user = User.objects.filter(phone__iexact = phone)

            if user.exists():
                return Response({
                    'status' : False,
                    'detail' : 'Phone number already exists.'
                    })
            else:
                key = send_otp(phone)

                if key:
                    old = Customer.objects.filter(phone__iexact = phone)
                    if old.exists():
                        old  = old.first()
                        count = old.count
                        # if count > 20:
                        #     return Response({
                        #         'status': False,
                        #         'detail' : 'Sending otp error. Limit Exceeded. Please contact customer support.'
                        #         })
                        old.count = count + 1
                        old.save()
                        print('Count Increase', count)
                        return Response({
                            'status' : True,
                            'detail' : 'OTP sent successfully.'
                            })
                    else:
                        PhoneOTP.objects.create(
                            # name = name,
                            phone = phone,
                            otp = key,

                            )
                        link = f'API-urls'
                        requests.get(link)
                        return Response({
                            'status' : True,
                            'detail' : 'OTP sent successfully.'
                            })



                else:
                    return Response({
                        'status' : False,
                        'detail' : 'Sending OTP error.'
                        })

        else:
            return Response({
                'status' : False,
                'detail' : 'Phone number is not given in post request.'
                })


def send_otp(phone):
    if phone:
        key = random.randint(999,9999)
        print(key)
        return key
    else:
        return False


class ValidateOTP(APIView):
    permission_classes = (permissions.AllowAny, )
    def post(self, request, *args, **kwargs):
        phone = request.data.get('phone' , False)
        otp_sent = request.data.get('otp', False)

        if phone and otp_sent:
            old = Phone.objects.filter(phone__iexact = phone)
            if old.exists():
                old = old.first()
                otp = old.otp
                if str(otp_sent) == str(otp):
                    old.validated = True
                    old.save()
                    return Response({
                        'status' : True,
                        'detail' : 'OTP mactched. Please proceed for registration.'
                        })

                else: 
                    return Response({
                        'status' : False,
                        'detail' : 'OTP incorrect.'
                        })
            else:
                return Response({
                    'status' : False,
                    'detail' : 'First proceed via sending otp request.'
                    })
        else:
            return Response({
                'status' : False,
                'detail' : 'Please provide both phone and otp for validations'
                })

In this way you can do verification of user by OTP

like image 63
shoaibdevs Avatar answered Jan 09 '23 23:01

shoaibdevs


models.py:

class CustomUser(AbstractUser):
    # username_validator = UnicodeUsernameValidator()
     
    username = models.CharField(max_length=80,unique=True)
    email = models.EmailField(unique=True)
    otp = models.IntegerField(null=True,blank=True)
    activation_key = models.CharField(max_length=150,blank=True,null=True)

urls.py:

urlpatterns = [
    path('signup/', signup,name = "sign_up"),
    path('signup_verify/<int:otp>/', signupVerify,name = "signup_verify"),
]

pip install pyotp

Docs : pyotp

Here use Time-based OTPs :

views.py:

from rest_framework import status
from django.contrib.auth import get_user_model
from .serializers import SignUpSerializer
from rest_framework.decorators import api_view, permission_classes
import pyotp
from rest_framework.response import Response
from rest_framework.permissions import AllowAny
from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError
from django.core.mail.message import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.conf import settings

User = get_user_model()

class generateKey:
    @staticmethod
    def returnValue():
        secret = pyotp.random_base32()        
        totp = pyotp.TOTP(secret, interval=86400)
        OTP = totp.now()
        return {"totp":secret,"OTP":OTP}


@api_view(['POST'])
@permission_classes([AllowAny,])
def signup(request):
    serializer = SignUpSerializer(data=request.data)
    
    if serializer.is_valid():
        
        key = generateKey.returnValue()
        user = User(
            username = serializer.data['username'],
            email = serializer.data['email'],
            otp = key['OTP'],
            activation_key = key['totp'],
        )
        
        try:
            validate_password(serializer.data['password'], user)
        except ValidationError as e:
            return Response(str(e), status=status.HTTP_400_BAD_REQUEST)
        
        user.set_password(serializer.data['password'])
        user.is_active = False
        user.save()
        
        email_template = render_to_string('signup_otp.html',{"otp":key['OTP'],"username":serializer.data['username']})    
        sign_up = EmailMultiAlternatives(
                        "Otp Verification", 
                        "Otp Verification",
                        settings.EMAIL_HOST_USER, 
                        [serializer.data['email']],
                    )
        sign_up.attach_alternative(email_template, 'text/html')
        sign_up.send()
    
        return Response(serializer.data, status=status.HTTP_201_CREATED)
    else:
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    
@api_view(['POST'])
@permission_classes([AllowAny,])
def signupVerify(request,otp):
    try:
        user = User.objects.get(otp = otp,is_active = False)
        _otp = user.otp
        if otp != _otp:
            return Response({"Otp" : "Invalid otp"},status=status.HTTP_406_NOT_ACCEPTABLE)
        else:
            activation_key = user.activation_key
            totp = pyotp.TOTP(activation_key, interval=86400)
            verify = totp.verify(otp)
            
            if verify:
                user.is_active = True
                user.save()
                
                email_template = render_to_string('signup_otp_success.html',{"username":user.username})    
                sign_up = EmailMultiAlternatives(
                        "Account successfully activated", 
                        "Account successfully activated",
                        settings.EMAIL_HOST_USER, 
                        [user.email],
                    )
                sign_up.attach_alternative(email_template, 'text/html')
                sign_up.send()
                
                return Response({"Varify success" : "Your account has been successfully activated!!"}, status=status.HTTP_202_ACCEPTED)
            else:
                return Response({"Time out" : "Given otp is expired!!"}, status=status.HTTP_408_REQUEST_TIMEOUT)
    
    except:
        return Response({"No User" : "Invalid otp OR No any inactive user found for given otp"}, status=status.HTTP_400_BAD_REQUEST)

signup_otp.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Otp Verification</title>
</head>
<body>
    <h1>Otp Verification</h1>
    <hr>
    <small>Hello, {{username}}</small>

    <p>Your Otp is <span style="font-weight: bolder; font-size: larger; background-color: rgb(230, 233, 236); padding: 4px;">{{otp}}</span></p>

    <p>This otp is valid for 1 day only..</p>
    <em>Thank you</em><br/>
    <em>Team <b>Company name</b></em>

</body>
</html>

signup_otp_success.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Account successfully activated</title>
</head>
<body>
    <h1>Welcome <strong>{{username}}</strong></h1>
    <hr>
    <p>Your account successfully activated.Now you can access all the feature of this site!!</p>
    <h6>Have a great day {{username}}</h6>
    <small>Team Company Name.</small>
</body>
</html>
like image 32
Pradip Avatar answered Jan 09 '23 23:01

Pradip