I proceeded with Phase 1 step 1 (phone-first auth + OTP hardening) and updated the plan context. Here’s the current state and the next steps.
What I implemented Phone-first auth endpoints with OTP → JWT issuance. views.py urls.py Phone normalization (KSA-focused, E.164 or Saudi mobile) and validation. phone.py serializers.py OTP protections: rate limit, resend cooldown, attempt counting. otp.py models.py Email is now optional to allow phone-only users. models.py 0002_phone_auth_fields.py Admin OTP visibility improved. admin.py Risks updated. risks.md
This commit is contained in:
@@ -2,6 +2,7 @@ from django.contrib.auth import get_user_model
|
||||
from rest_framework import serializers
|
||||
|
||||
from apps.accounts.models import OtpChannel, PhoneOTP
|
||||
from apps.accounts.services.phone import normalize_phone_number
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
@@ -20,6 +21,14 @@ class RegisterSerializer(serializers.ModelSerializer):
|
||||
model = User
|
||||
fields = ["email", "password", "phone_number", "first_name", "last_name"]
|
||||
|
||||
def validate_phone_number(self, value):
|
||||
if not value:
|
||||
return value
|
||||
try:
|
||||
return normalize_phone_number(value)
|
||||
except ValueError as exc:
|
||||
raise serializers.ValidationError(str(exc)) from exc
|
||||
|
||||
def create(self, validated_data):
|
||||
return User.objects.create_user(**validated_data)
|
||||
|
||||
@@ -28,12 +37,37 @@ class OTPRequestSerializer(serializers.Serializer):
|
||||
phone_number = serializers.CharField(max_length=20)
|
||||
channel = serializers.ChoiceField(choices=OtpChannel.choices)
|
||||
|
||||
def validate_phone_number(self, value):
|
||||
try:
|
||||
return normalize_phone_number(value)
|
||||
except ValueError as exc:
|
||||
raise serializers.ValidationError(str(exc)) from exc
|
||||
|
||||
|
||||
class OTPVerifySerializer(serializers.Serializer):
|
||||
request_id = serializers.UUIDField()
|
||||
code = serializers.CharField(max_length=6)
|
||||
|
||||
|
||||
class PhoneAuthRequestSerializer(serializers.Serializer):
|
||||
phone_number = serializers.CharField(max_length=20)
|
||||
channel = serializers.ChoiceField(choices=OtpChannel.choices)
|
||||
email = serializers.EmailField(required=False, allow_null=True, allow_blank=True)
|
||||
first_name = serializers.CharField(required=False, allow_blank=True)
|
||||
last_name = serializers.CharField(required=False, allow_blank=True)
|
||||
|
||||
def validate_phone_number(self, value):
|
||||
try:
|
||||
return normalize_phone_number(value)
|
||||
except ValueError as exc:
|
||||
raise serializers.ValidationError(str(exc)) from exc
|
||||
|
||||
|
||||
class PhoneAuthVerifySerializer(serializers.Serializer):
|
||||
request_id = serializers.UUIDField()
|
||||
code = serializers.CharField(max_length=6)
|
||||
|
||||
|
||||
class OTPStatusSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = PhoneOTP
|
||||
|
||||
Reference in New Issue
Block a user