fix: validate existing phone/username upfront and prevent duplicate registration OTP dispatch
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 36s

This commit is contained in:
vickytechkey 2026-11-01 13:40:26 +05:30
parent 1252fb12b2
commit 414fbffbcf
2 changed files with 69 additions and 31 deletions

View file

@ -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(

View file

@ -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)