harden OTP for real SMS and close the second audit round
Kavenegar is being switched on, so every OTP request now costs credit: - phone numbers are normalised to 09XXXXXXXXX everywhere (login, OTP, family invite, TEST_LOGIN_PHONES keys) and existing rows are migrated; non-Iranian and landline numbers are rejected before any SMS - cooldown and a daily per-phone cap are counted in the database, so the three gunicorn workers no longer triple them - a global daily OTP SMS cap (default 300) stops SMS pumping - per-IP rates widened for CGNAT / school Wi-Fi (env-overridable) - provider message id is logged; SMS text warns not to share the code Second audit round: an offer no longer shortens a longer paid plan, Zarinpal error bodies no longer 500, island XP reaches the parent report, a non-numeric questionId no longer 500s after the answer is generated. 596 tests green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -514,8 +514,9 @@ class LessonCompleteView(APIView):
|
||||
# میتونست بهجای هر کاربرِ دیگری xp/coins ثبت کنه.
|
||||
permission_classes = [IsAuthenticated]
|
||||
# Idempotency-Key فقط جلوی retry با همون کلید رو میگیره؛ برای همینه که
|
||||
# GuestAwareContentThrottle لازمه (نگاه کنید به تعریفش بالا). نرخ ۱۰/hour
|
||||
# (نه ۶۰/min مثلِ question_answer) چون این endpoint رکوردِ ماندگار میسازه.
|
||||
# GuestAwareContentThrottle لازمه (نگاه کنید به تعریفش بالا). نرخ در
|
||||
# settings (`lesson_complete`، الان ۱۲۰/hour) کمتر از question_answer است
|
||||
# چون این endpoint رکوردِ ماندگار میسازه.
|
||||
throttle_scope = 'lesson_complete'
|
||||
throttle_classes = [GuestAwareContentThrottle]
|
||||
|
||||
@@ -554,6 +555,9 @@ class LessonCompleteView(APIView):
|
||||
# امتیاز فقط برای جوابهایی که واقعاً ثبت شدهاند. پیشتر عددِ کلاینت
|
||||
# (فقط با سقفِ تعدادِ سوالها) پذیرفته میشد، پس صدا زدنِ مستقیمِ همین
|
||||
# endpoint برای هر درس، بیجواب دادن، امتیاز و جدولِ رتبهبندی را پر میکرد.
|
||||
# ⚠️ جلوی «غلط بزن، کلید را ببین، درست بزن» را نمیگیرد
|
||||
# (verified_correct_count را ببینید)، و همین جوابها در پایانِ آزمونِ
|
||||
# جزیره دوباره امتیاز میگیرند. هر دو در بازبینیِ ۲۰۲۶-۰۹-۲۵ بازتولید شد.
|
||||
correct_count = min(correct_count, verified_correct_count(request.user.id, lesson_id=lesson.id))
|
||||
|
||||
# ratio همیشه بر اساس actual_question_count محاسبه میشود، نه totalQuestions ارسالی کلاینت.
|
||||
@@ -883,9 +887,12 @@ class NodeQuizCompleteView(APIView):
|
||||
correct_count = serializer.validated_data['correctCount']
|
||||
total_questions = serializer.validated_data['totalQuestions']
|
||||
|
||||
# سقفها از روی خودِ مبحث، نه از روی حرفِ کلاینت، تا امتیازِ متورم
|
||||
# ثبت نشود: آزمون بیشتر از استخرِ سوالهای مبحث (و سقفِ NodeQuizView)
|
||||
# سوال ندارد، و پاسخِ درست بیشتر از سوالها نمیشود.
|
||||
# سقفها از روی خودِ مبحث، نه از روی حرفِ کلاینت: آزمون بیشتر از
|
||||
# استخرِ سوالهای مبحث (و سقفِ NodeQuizView) سوال ندارد، و پاسخِ درست
|
||||
# بیشتر از سوالها نمیشود.
|
||||
# ⚠️ سقف «کلِ استخر» است (تا ۲۰)، نه آزمونِ چهارسوالیای که واقعاً داده
|
||||
# شد؛ جوابهای درستِ مسیرِ درسها هم اینجا دوباره شمرده میشوند. قفل بودنِ
|
||||
# جزیره هم بررسی نمیشود: هر جزیرهای با correctCount=0 «تمام» میشود.
|
||||
pool = min(
|
||||
Question.objects.filter(lesson__node_id=node_id).count(),
|
||||
NodeQuizView.MAX_SIZE,
|
||||
|
||||
Reference in New Issue
Block a user