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:
@@ -36,6 +36,8 @@
|
||||
| ۱۴ | SQLite روی دیسکِ مشترکِ پُر، بدونِ بکاپِ خودکارِ بیرون از ماشین | کم (داده) | زیرساخت |
|
||||
| ۱۵ | دسترسیِ سرور: حسابِ مشترک، رمز، sudoِ کامل | بالا | ادمینِ سرور |
|
||||
| ۱۶ | فرانت توکن را در `localStorage` نگه میدارد | کم | فرانت |
|
||||
| ۱۷ | ~~شمارهی موبایل نرمال نمیشود؛ سقفهای per-phone با املای دیگر دور زده میشوند~~ **بسته شد ۲۰۲۶-۰۹-۲۵** | متوسط | کد |
|
||||
| ۱۸ | `/api/payment/orders/{id}/pay` بدونِ ورود و بدونِ سنجشِ مالکیت | کم | کد |
|
||||
|
||||
---
|
||||
|
||||
@@ -206,6 +208,43 @@
|
||||
- **خطر:** هر XSS در سایت هر دو را میخواند.
|
||||
- **رفع:** CSPِ سختگیرانه روی `math.tadrisino2.ir`؛ در بلندمدت refresh در کوکیِ `HttpOnly`.
|
||||
|
||||
## ۱۷. شمارهی موبایل نرمال نمیشود (افزوده در بازبینیِ دومِ ۲۰۲۶-۰۹-۲۵)
|
||||
|
||||
- **چیست:** `phone` در ورود، درخواستِ OTP و دعوتِ خانواده رشتهی خام است
|
||||
(`accounts/serializers.py`، `max_length=15` بدونِ اعتبارسنجی). `09121234567`،
|
||||
`+989121234567`، `989121234567` و همان شماره با ارقامِ فارسی چهار «شماره»ی جدااند.
|
||||
- **خطر:** cooldownِ ۲ دقیقهای و سقفِ ۵ در ساعتِ هر شماره بهازای هر املا جداست، پس با
|
||||
چند IP میشود یک گوشی را با پیامک بمباران کرد (وقتی کاوهنگار وصل شود، به هزینهی ما)؛
|
||||
یک آدم با دو املا دو حسابِ جدا میسازد؛ دعوتِ والد با املای دیگر ۴۰۳ میگیرد.
|
||||
(بازتولید شد با تستِ موقت؛ پیامکِ واقعی فرستاده نشد.)
|
||||
- **رفع:** یک تابعِ `normalize_phone` (ارقامِ فارسی/عربی → لاتین، `+98`/`0098`/`98` → `0`،
|
||||
فقط `09\d{9}`) در هر سه serializer، و migrationِ داده برای ردیفهای موجود.
|
||||
- **چطور بسنجید:** دو درخواستِ OTP پشتِسرِهم با `0912…` و `+98912…` باید دومی ۴۲۹ بگیرد.
|
||||
- **✅ بسته شد ۲۰۲۶-۰۹-۲۵** (همان روزی که کلیدِ کاوهنگار روی سرور رفت؛ کامیت: _پس از کامیت
|
||||
اینجا بنویسید_). `accounts/phone.py::normalize_phone` در ورود، درخواستِ OTP، دعوتِ
|
||||
خانواده و کلیدهای `TEST_LOGIN_PHONES`؛ migrationِ `accounts.0005` شمارههای موجود را
|
||||
نرمال میکند (ردیفی که شکلِ نرمالش مالِ کاربرِ دیگری است دست نمیخورد و فقط شمرده میشود).
|
||||
`parentPhone` در پروفایل هنوز نرمال **نمیشود**. همراهش، چون از این تاریخ پیامکِ واقعی
|
||||
خرجِ اعتبار است:
|
||||
- cooldownِ ۲ دقیقهای و سقفِ روزانهی هر شماره (`OTP_REQUEST_PHONE_DAILY_MAX=10`)
|
||||
حالا در **دیتابیس** هم شمرده میشوند، پس روی ۳ workerِ سرور سهبرابر نمیشوند (بندِ ۹
|
||||
برای این دو دیگر صدق نمیکند؛ برای throttleهای IP هنوز میکند).
|
||||
- سقفِ روزانهی کلِ پیامکِ OTP (`OTP_SMS_GLOBAL_DAILY_MAX=300`) جلوی SMS pumping؛
|
||||
رسیدن به آن ۵۰۳ و لاگِ ERROR میدهد. با رشدِ کاربرِ واقعی از `.env` بالا ببرید.
|
||||
- سقفهای per-IP گشادهتر شدند (کد ۲۰، ورود ۳۰، مهمان ۶۰ در ساعت، همه از `.env` قابلِ
|
||||
تغییر) چون پشتِ CGNAT و وایفایِ مدرسه دهها نفر یک IP دارند.
|
||||
- تستها: `accounts/tests_otp_limits.py`. همزمانیِ واقعیِ دو پروسهی gunicorn تست نشده.
|
||||
|
||||
## ۱۸. صفحهی پرداخت مالکیت را نمیسنجد (افزوده در بازبینیِ دومِ ۲۰۲۶-۰۹-۲۵)
|
||||
|
||||
- **چیست:** `PaymentRedirectView` (`AllowAny`) برای هر شناسهی سفارشِ ترتیبی از زرینپال
|
||||
authority میگیرد.
|
||||
- **خطر:** غریبه با پیمایشِ `/orders/1/pay`، `/orders/2/pay`… روی سفارشهای باز authority
|
||||
مینشاند؛ از آن به بعد صاحبِ سفارش «کدِ تخفیف را پیش از درگاه وارد کنید» میگیرد
|
||||
(بازتولید شد، با زرینپالِ mock). وضعیتِ paid/failedِ سفارشِ دیگران هم از ریدایرکت پیداست.
|
||||
- **رفع:** در `paymentUrl` یک توکنِ امضاشدهی کوتاهعمر (`signing`) با `order_id` بگذارید و
|
||||
همانجا بسنجید؛ یا شناسهی غیرترتیبی.
|
||||
|
||||
---
|
||||
|
||||
## چیزهایی که بسته شدهاند (برای اینکه دوباره باز نشوند)
|
||||
|
||||
Reference in New Issue
Block a user