From 414fbffbcf95be8667471a67ec94dd31558b8daa Mon Sep 17 00:00:00 2001 From: vickytechkey Date: Sun, 1 Nov 2026 13:40:26 +0530 Subject: [PATCH] fix: validate existing phone/username upfront and prevent duplicate registration OTP dispatch --- api/serializers.py | 83 +++++++++++++++++++++++++++++----------------- api/views.py | 17 +++++++++- 2 files changed, 69 insertions(+), 31 deletions(-) diff --git a/api/serializers.py b/api/serializers.py index 6ba72e4..cdda8cf 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -205,23 +205,18 @@ class RegisterSerializer(serializers.Serializer): if phone_search: q |= Q(username=f"user_{phone_search}") | Q(username=phone_search) - existing_user = User.objects.filter(q).first() - if existing_user: + existing_users = User.objects.filter(q) + for existing_user in existing_users: if SupplierProfile.objects.filter(user=existing_user).exists() or SupplierOnboardingDraft.objects.filter(user=existing_user).exists(): raise serializers.ValidationError("Account already registered with this username/mobile. Please log in.") - else: - existing_user.delete() return value def validate_email(self, value): if not value: return value existing_user = User.objects.filter(email__iexact=value).first() - if existing_user: - if not SupplierProfile.objects.filter(user=existing_user).exists(): - existing_user.delete() - else: - raise serializers.ValidationError("Email already registered.") + if existing_user and (SupplierProfile.objects.filter(user=existing_user).exists() or SupplierOnboardingDraft.objects.filter(user=existing_user).exists()): + raise serializers.ValidationError("Email already registered. Please log in.") return value def validate_phone(self, value): @@ -233,32 +228,28 @@ class RegisterSerializer(serializers.Serializer): # Check existing profile (phone, support_phone, or user username) existing_profile = SupplierProfile.objects.filter( Q(phone__icontains=phone_search) | - Q(support_phone__icontains=phone_search) | + Q(support_phone__icontains=phone_search) | Q(user__username__icontains=phone_search) ).first() if existing_profile: raise serializers.ValidationError("Mobile number is already registered. Please log in instead.") - # Check existing user - existing_user = User.objects.filter( - Q(username__icontains=phone_search) | Q(username=phone_search) - ).first() - if existing_user: - if SupplierProfile.objects.filter(user=existing_user).exists(): + # Check existing users matching phone + matching_users = User.objects.filter( + Q(username__icontains=phone_search) | Q(username=phone_search) | Q(username=f"user_{phone_search}") + ) + for u in matching_users: + if SupplierProfile.objects.filter(user=u).exists(): raise serializers.ValidationError("Mobile number is already registered. Please log in instead.") - draft = SupplierOnboardingDraft.objects.filter(user=existing_user).first() - if draft: + if SupplierOnboardingDraft.objects.filter(user=u).exists(): raise serializers.ValidationError("An account with this mobile number already exists. Please log in to continue.") - existing_draft = SupplierOnboardingDraft.objects.filter(phone__icontains=phone_search).first() + existing_draft = SupplierOnboardingDraft.objects.filter( + Q(phone__icontains=phone_search) | Q(user__username__icontains=phone_search) + ).first() if existing_draft: raise serializers.ValidationError("An account with this mobile number already exists. Please log in to continue.") - # Clean up any orphaned auth_user record with this username/phone if no profile or draft exists - orphans = User.objects.filter(username__icontains=phone_search) - for orphan in orphans: - if not SupplierProfile.objects.filter(user=orphan).exists() and not SupplierOnboardingDraft.objects.filter(user=orphan).exists(): - orphan.delete() return value def validate_password(self, value): @@ -271,6 +262,31 @@ class RegisterSerializer(serializers.Serializer): digits = ''.join(c for c in str(phone) if c.isdigit()) clean_phone = digits[-10:] if len(digits) >= 10 else digits + if clean_phone: + if SupplierProfile.objects.filter( + Q(phone__icontains=clean_phone) | + Q(support_phone__icontains=clean_phone) | + Q(user__username__icontains=clean_phone) + ).exists(): + raise serializers.ValidationError({"phone": "Mobile number is already registered. Please log in instead."}) + + if SupplierOnboardingDraft.objects.filter( + Q(phone__icontains=clean_phone) | + Q(user__username__icontains=clean_phone) + ).exists(): + raise serializers.ValidationError({"phone": "An account with this mobile number already exists. Please log in to continue."}) + + matching_users = User.objects.filter( + Q(username=clean_phone) | + Q(username=f"user_{clean_phone}") | + Q(username__icontains=clean_phone) + ) + for u in matching_users: + if SupplierProfile.objects.filter(user=u).exists(): + raise serializers.ValidationError({"phone": "Mobile number is already registered. Please log in instead."}) + if SupplierOnboardingDraft.objects.filter(user=u).exists(): + raise serializers.ValidationError({"phone": "An account with this mobile number already exists. Please log in to continue."}) + if not data.get('email') or data.get('email').endswith('@tradhox.com'): data['email'] = "" if not data.get('password'): @@ -290,13 +306,20 @@ class RegisterSerializer(serializers.Serializer): email_val = validated_data['email'] phone_val = validated_data['phone'] - # Purge leftover orphan user records matching username or email without profile + # Purge leftover orphan user records matching username or email without profile or draft + # Use raw SQL to avoid multi-database cross-relation CASCADE delete issues + from django.db import connection for u in User.objects.filter(username__iexact=username_val): - if not SupplierProfile.objects.filter(user=u).exists(): - u.delete() - for u in User.objects.filter(email__iexact=email_val): - if not SupplierProfile.objects.filter(user=u).exists(): - u.delete() + if not SupplierProfile.objects.filter(user=u).exists() and not SupplierOnboardingDraft.objects.filter(user=u).exists(): + with connection.cursor() as cursor: + cursor.execute("DELETE FROM api_otprecord WHERE user_id = %s", [u.id]) + cursor.execute("DELETE FROM auth_user WHERE id = %s", [u.id]) + if email_val: + for u in User.objects.filter(email__iexact=email_val): + if not SupplierProfile.objects.filter(user=u).exists() and not SupplierOnboardingDraft.objects.filter(user=u).exists(): + with connection.cursor() as cursor: + cursor.execute("DELETE FROM api_otprecord WHERE user_id = %s", [u.id]) + cursor.execute("DELETE FROM auth_user WHERE id = %s", [u.id]) try: user = User.objects.create_user( diff --git a/api/views.py b/api/views.py index 775cf47..8f452b1 100644 --- a/api/views.py +++ b/api/views.py @@ -16,7 +16,7 @@ from django.utils import timezone from django.contrib.auth import authenticate from django.contrib.auth.models import User from django.utils.text import slugify -from rest_framework import viewsets, status, generics +from rest_framework import viewsets, status, generics, serializers from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.decorators import action as drf_action @@ -326,6 +326,21 @@ class SendWhatsAppOtpView(APIView): else: clean_number = digits + # If requesting registration OTP, ensure phone is not already registered + phone_search = digits[-10:] if len(digits) >= 10 else digits + if template_name == 'mobile_number_verification' and phone_search: + is_already_registered = SupplierProfile.objects.filter( + Q(phone__icontains=phone_search) | + Q(support_phone__icontains=phone_search) | + Q(user__username__icontains=phone_search) + ).exists() + if is_already_registered: + return Response({ + 'error': 'Mobile number is already registered. Please log in instead.', + 'detail': 'Mobile number is already registered. Please log in instead.', + 'is_registered': True + }, status=status.HTTP_400_BAD_REQUEST) + user = request.user if (request.user and request.user.is_authenticated) else None if not user and digits: user = resolve_user_by_identifier(digits)