Files
MathApp/accounts/migrations/0005_normalize_user_phones.py
Zareian 0580aec7b0 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>
2026-09-25 12:28:06 +03:30

41 lines
1.8 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
شماره‌های موجود را به همان شکلِ 09XXXXXXXXX می‌برد که serializerها حالا می‌سازند
(accounts/phone.py)؛ وگرنه کسی که با ‎+98…‎ ثبت‌نام کرده بود دیگر به حسابِ خودش
نمی‌رسید و حسابِ تازه می‌گرفت.
ردیفی که شکلِ نرمالش از قبل مالِ کاربرِ دیگری است دست نمی‌خورد (unique است و
ادغامِ دو حساب تصمیمِ انسانی است)؛ ردیفی هم که موبایلِ ایران نیست همان‌طور می‌ماند.
هر دو فقط شمرده و چاپ می‌شوند. برگشت‌ناپذیر است چون شکلِ قبلی ذخیره نمی‌شود.
"""
from django.db import migrations
def normalize(apps, schema_editor):
from accounts.phone import normalize_phone
User = apps.get_model('accounts', 'User')
changed = skipped_taken = skipped_invalid = 0
for user in User.objects.exclude(phone__isnull=True).exclude(phone=''):
target = normalize_phone(user.phone)
if target is None:
skipped_invalid += 1
elif target != user.phone:
if User.objects.filter(phone=target).exists():
skipped_taken += 1
else:
User.objects.filter(pk=user.pk).update(phone=target)
changed += 1
if changed or skipped_taken or skipped_invalid:
print(f'\n normalize phones: changed={changed} taken={skipped_taken} invalid={skipped_invalid}')
class Migration(migrations.Migration):
dependencies = [
('accounts', '0004_otpcode_failed_attempts_user_tokens_valid_after'),
]
operations = [
migrations.RunPython(normalize, migrations.RunPython.noop),
]