【问题标题】:OTP Verification in Django Rest FrameworkDjango Rest 框架中的 OTP 验证
【发布时间】:2020-08-29 08:22:58
【问题描述】:

我正在尝试制作一个 django 应用程序,我想在其中创建一个 opt 验证,但我很困惑什么是正确的方法。这是我到目前为止所做的:

模型.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)

序列化器.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

如何使用此 otp 验证用户? 我的方法是,如果创建了用户并且他尝试登录,那么显然他未被验证为verified = models.BooleanField(default=False),因此他会看到一个弹出窗口以输入他在邮件中收到的 otp,如果 otp 匹配,他可以继续并登录

Views.py

验证 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")

请建议我应该如何进行,使用相同的otp 字段重置密码是否明智?

【问题讨论】:

    标签: python django django-rest-framework one-time-password


    【解决方案1】:

    首先,创建一个PhoneOTP模型

    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)
    

    然后创建一个OTP生成视图并验证视图

    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'
                    })
    

    这样你就可以通过OTP对用户进行验证

    【讨论】:

      【解决方案2】:

      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

      文档:pyotp

      这里使用基于时间的 OTP:

      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>
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-07-06
        • 2013-06-29
        • 2017-09-26
        • 2017-11-02
        • 2019-07-11
        • 2019-03-16
        相关资源
        最近更新 更多