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:
40
accounts/migrations/0005_normalize_user_phones.py
Normal file
40
accounts/migrations/0005_normalize_user_phones.py
Normal file
@@ -0,0 +1,40 @@
|
||||
"""
|
||||
شمارههای موجود را به همان شکلِ 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),
|
||||
]
|
||||
Reference in New Issue
Block a user