From e0a80d4a007e5f48d5835c4d9f496c081bad3efc Mon Sep 17 00:00:00 2001 From: i701 Date: Tue, 15 Apr 2025 14:01:47 +0500 Subject: [PATCH] registration and verify abuse WIP --- api/models.py | 18 ++++++++++++++++++ api/serializers.py | 5 +++++ api/signals.py | 9 +++++++++ api/tasks.py | 41 +++++++++++++++++++++++++++++++++++++++++ api/views.py | 13 ++++++++----- requirements.txt | 20 +++++++++++++++++++- 6 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 api/tasks.py diff --git a/api/models.py b/api/models.py index 22accab..8dbb93b 100644 --- a/api/models.py +++ b/api/models.py @@ -2,10 +2,12 @@ This is the models module for api. """ +from datetime import timedelta from django.contrib.auth.models import AbstractUser from django.db import models from .managers import CustomUserManager from django.utils import timezone +import pyotp class User(AbstractUser): @@ -34,6 +36,22 @@ class User(AbstractUser): objects = CustomUserManager() +class TemporaryUser(User): + otp_secret = models.CharField(max_length=50, default=pyotp.random_base32) + otp_verified = models.BooleanField(default=False) + + def generate_otp(self): + totp = pyotp.TOTP(self.otp_secret, interval=300) + return totp.now() + + def verify_otp(self, otp): + totp = pyotp.TOTP(self.otp_secret, interval=300) + return totp.verify(otp) + + def is_expired(self): + return self.created_at < timezone.now() - timedelta(minutes=5) + + class Atoll(models.Model): name = models.CharField(max_length=255, unique=True) created_at = models.DateTimeField(default=timezone.now) diff --git a/api/serializers.py b/api/serializers.py index ab40653..eb724e4 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -139,3 +139,8 @@ class AtollSerializer(serializers.ModelSerializer): model = Atoll fields = "__all__" depth = 2 + + +class OTPVerificationSerializer(serializers.Serializer): + mobile = serializers.CharField() + otp = serializers.CharField() diff --git a/api/signals.py b/api/signals.py index 7024a7e..a5f0a9e 100644 --- a/api/signals.py +++ b/api/signals.py @@ -6,6 +6,8 @@ from django_rest_passwordreset.signals import reset_password_token_created from django.db.models.signals import post_save from api.models import User from django.contrib.auth.models import Permission +from api.tasks import verify_user_with_person_api_task +from asgiref.sync import sync_to_async @receiver(post_save, sender=User) @@ -26,6 +28,13 @@ def assign_device_permissions(sender, instance, created, **kwargs): instance.user_permissions.add(permission) +@receiver(post_save, sender=User) +@sync_to_async +def verify_user_with_person_api(sender, instance, created, **kwargs): + if created: + verify_user_with_person_api_task(instance.id) + + @receiver(reset_password_token_created) def password_reset_token_created( sender, instance, reset_password_token, *args, **kwargs diff --git a/api/tasks.py b/api/tasks.py new file mode 100644 index 0000000..8f47ac9 --- /dev/null +++ b/api/tasks.py @@ -0,0 +1,41 @@ +from django.shortcuts import get_object_or_404 +from api.models import User +import requests +from apibase.env import env, BASE_DIR +import os + +PERSON_API_URL = env.str("PERSON_VERIFY_BASE_URL", "") + +env.read_env(os.path.join(BASE_DIR, ".env")) + + +def verify_user_with_person_api_task(user_id: int): + """ + Verify the user with the Person API. + :param user_id: The ID of the user to verify. + """ + + user = get_object_or_404(User, id=user_id) + # Call the Person API to verify the user + response = requests.get(f"{PERSON_API_URL}/api/person/{user.id_card}") + if response.status_code == 200: + data = response.json() + print(f"Data from Person API: {data}") + print("Data from of user: ", user.__dict__) + if ( + data.get("nic") == user.id_card + and data.get("name_en") == f"{user.first_name} {user.last_name}" + and data.get("house_name_en") == user.address + and data.get("dob") == user.dob.isoformat() + ): + user.verified = True + user.save() + return True + else: + user.verified = False + user.save() + return False + else: + # Handle the error case + print(f"Error verifying user: {response.status_code} - {response.text}") + return False diff --git a/api/views.py b/api/views.py index 88b6145..e42bd25 100644 --- a/api/views.py +++ b/api/views.py @@ -43,6 +43,7 @@ ACCOUNT_NUMBER_PATTERN = r"^(7\d{12}|9\d{16})$" class ErrorMessages: USERNAME_EXISTS = "Username already exists." + MOBILE_EXISTS = "Mobile number already exists." INVALID_ID_CARD = "Please enter a valid ID card number." INVALID_MOBILE = "Please enter a valid mobile number." INVALID_ACCOUNT = "Please enter a valid account number." @@ -99,12 +100,9 @@ class CreateUserView(generics.CreateAPIView): firstname = request.data.get("firstname") lastname = request.data.get("lastname") - # Validate required fields first - validation_error = self.validate_required_fields(request.data) - if validation_error: - return validation_error + if User.objects.filter(mobile=mobile).exists(): + return Response({"message": ErrorMessages.MOBILE_EXISTS}, status=400) - # Check username uniqueness after validation if User.objects.filter(username=username).exists(): return Response({"message": ErrorMessages.USERNAME_EXISTS}, status=400) @@ -120,6 +118,11 @@ class CreateUserView(generics.CreateAPIView): if acc_no is None or not re.match(ACCOUNT_NUMBER_PATTERN, acc_no): return Response({"message": ErrorMessages.INVALID_ACCOUNT}, status=400) + # Validate required fields first + validation_error = self.validate_required_fields(request.data) + if validation_error: + return validation_error + # Fetch Atoll and Island instances try: atoll = Atoll.objects.get(id=atoll_id) diff --git a/requirements.txt b/requirements.txt index 0201b14..de14cd8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,24 +2,31 @@ aiohappyeyeballs==2.4.3 aiohttp==3.11.2 aiohttp-retry==2.8.3 aiosignal==1.3.1 +amqp==5.3.1 +anyio==4.9.0 arabic-reshaper==3.0.0 asgiref==3.8.1 asn1crypto==1.5.1 async-timeout==4.0.3 attrs==24.2.0 +billiard==4.2.1 black==23.12.1 boto3==1.35.49 botocore==1.35.49 +celery==5.5.1 certifi==2024.2.2 cffi==1.16.0 chardet==5.2.0 charset-normalizer==3.3.2 click==8.1.7 +click-didyoumean==0.3.1 +click-plugins==1.1.1 +click-repl==0.3.0 colorama==0.4.6 cryptography==41.0.7 cssselect2==0.7.0 dj-database-url==2.1.0 -django==5.1.2 +django==5.2 django-cors-headers==4.3.1 django-debug-toolbar==4.2.0 django-easy-audit==1.3.7 @@ -38,15 +45,20 @@ djangorestframework-simplejwt==5.3.1 djangorestframework-stubs==3.15.1 dnspython==2.4.2 drf-spectacular==0.27.2 +exceptiongroup==1.2.2 faker==30.8.0 frozenlist==1.5.0 gunicorn==23.0.0 +h11==0.14.0 html5lib==1.1 +httpcore==1.0.7 +httpx==0.28.1 idna==3.6 inflection==0.5.1 jmespath==1.0.1 jsonschema==4.23.0 jsonschema-specifications==2024.10.1 +kombu==5.5.2 lxml==5.1.0 multidict==6.1.0 mypy-extensions==1.0.0 @@ -55,6 +67,7 @@ packaging==23.2 pathspec==0.12.1 pillow==10.2.0 platformdirs==4.1.0 +prompt-toolkit==3.0.50 propcache==0.2.0 psycopg==3.2.3 psycopg-binary==3.2.3 @@ -70,6 +83,7 @@ pypng==0.20220715.0 python-bidi==0.4.2 python-dateutil==2.9.0.post0 python-decouple==3.8 +python-telegram-bot==22.0 pytz==2023.3.post1 pyyaml==6.0.1 qrcode==7.4.2 @@ -81,11 +95,13 @@ rpds-py==0.21.0 ruff==0.7.0 s3transfer==0.10.3 six==1.16.0 +sniffio==1.3.1 sqlparse==0.5.1 svglib==1.5.1 tinycss2==1.2.1 tomli==2.0.2 toposort==1.10 +twilio==9.3.7 types-pyyaml==6.0.12.20240917 types-requests==2.32.0.20241016 typing-extensions==4.12.2 @@ -94,6 +110,8 @@ tzlocal==5.2 uritemplate==4.1.1 uritools==4.0.2 urllib3==2.2.1 +vine==5.1.0 +wcwidth==0.2.13 webencodings==0.5.1 whitenoise==6.7.0 yarl==1.17.2