The API moved to mathapi.tadrisino2.ir on 2026-09-26 (nginx server_name changed at 23:22) and api.math.tadrisino2.ir no longer resolves. The code default for PUBLIC_BASE_URL and the docs' curl examples still named the dead host, so every paymentUrl, the Zarinpal callback and the Bazaar redirect the backend builds pointed nowhere. Historical status notes in CLAUDE.md keep the old name on purpose. config/tests.py now reads the API host from PUBLIC_BASE_URL instead of a literal. 624 tests green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
263 lines
23 KiB
Markdown
263 lines
23 KiB
Markdown
# پیش از پروداکشنِ واقعی: چیزهایی که عمداً باز ماندهاند
|
||
|
||
**تاریخِ ثبت: ۲۰۲۶-۰۹-۲۵ (۳ مهر ۱۴۰۵)، کامیتِ پایه `66066cb`.**
|
||
|
||
این پروژه تا این تاریخ در حالتِ **دمو و ارائهی کارآموزی** است، نه محصولِ نهایی. چند چیز
|
||
عمداً باز گذاشته شده تا توسعهدهندهها، فرانتکار و ارائه کار کنند. هر کدام برای یک سرورِ
|
||
عمومی با کاربرِ واقعی (کودک، پرداختِ واقعی) **خطر است** و باید پیش از آمدنِ اولین کاربرِ
|
||
واقعی بسته شود.
|
||
|
||
**برای agentها و توسعهدهندههای بعدی:** این فهرست ادعاست، نه ضمانت؛ ممکن است از این
|
||
تاریخ به بعد چیزی عوض شده باشد. پیش از بستنِ هر مورد، وضعیتِ فعلیاش را با خودِ سرور یا
|
||
کد بسنجید (ستونِ «چطور بسنجید»). بعد از بستنِ هر مورد، همینجا تاریخ و کامیتِ رفعش را
|
||
بنویسید؛ مورد را پاک نکنید.
|
||
|
||
هر آزمونِ «از بیرون» با `curl` بدونِ توکن انجام شده و در تاریخِ بالا تأیید شده است.
|
||
|
||
---
|
||
|
||
## خلاصه، به ترتیبِ خطر
|
||
|
||
| # | مورد | خطر | کارِ کیست |
|
||
|---|---|---|---|
|
||
| ۱ | کلیدِ امضای توکن روی سرور همان رشتهی داخلِ ریپو است | بحرانی — جعلِ ورود بهجای هر کاربر | کد آماده؛ اعمال روی سرور |
|
||
| ۲ | ۷ حسابِ دمو با کدِ ورودِ ثابت روی پروداکشن | بالا | `.env` سرور |
|
||
| ۳ | دادهی دمو و حسابِ مهمانِ عمومیِ `frontend-test-device` | بالا | دیتابیسِ سرور |
|
||
| ۴ | `/api/health` جزئیاتِ داخلی را به همه نشان میدهد | متوسط | کد |
|
||
| ۵ | Swagger، ReDoc و schemaی کاملِ API عمومیاند | متوسط | کد |
|
||
| ۶ | جدولِ رتبهبندی نام و شناسهی کودکان را بدونِ ورود نشان میدهد | متوسط (حریمِ خصوصیِ کودک) | کد |
|
||
| ۷ | پنلِ `/admin/` روی دامنهی عمومی | متوسط | nginx + کد |
|
||
| ۸ | سرور بدونِ ArvanCloud روی پورتِ ۸۰ هم جواب میدهد | متوسط | ادمینِ سرور |
|
||
| ۹ | کشِ مشترک (Redis) نیست؛ محدودیتها per-workerاند | متوسط | `.env` سرور |
|
||
| ۱۰ | ورودِ مهمان فقط با `deviceId` است | کم (طراحی) | کد |
|
||
| ۱۱ | توکنِ refresh و کدِ OTP بیهش در دیتابیس | کم | کد |
|
||
| ۱۲ | هزینهی دستیارِ AI با ساختِ حسابِ مهمان قابلِ بالا بردن است | کم (مالی) | کد |
|
||
| ۱۳ | محدودیتِ قلب در سرور خاموش است (`HEARTS_ENFORCED=0`) | کم (کسبوکار) | `.env` + فرانت |
|
||
| ۱۴ | SQLite روی دیسکِ مشترکِ پُر، بدونِ بکاپِ خودکارِ بیرون از ماشین | کم (داده) | زیرساخت |
|
||
| ۱۵ | دسترسیِ سرور: حسابِ مشترک، رمز، sudoِ کامل | بالا | ادمینِ سرور |
|
||
| ۱۶ | فرانت توکن را در `localStorage` نگه میدارد | کم | فرانت |
|
||
| ۱۷ | ~~شمارهی موبایل نرمال نمیشود؛ سقفهای per-phone با املای دیگر دور زده میشوند~~ **بسته شد ۲۰۲۶-۰۹-۲۵** | متوسط | کد |
|
||
| ۱۸ | `/api/payment/orders/{id}/pay` بدونِ ورود و بدونِ سنجشِ مالکیت | کم | کد |
|
||
|
||
---
|
||
|
||
## ۱. کلیدِ امضای توکن (`DJANGO_SECRET_KEY`) روی سرور
|
||
|
||
- **چیست:** توکنِ ورود فقط `user_id`ِ امضاشده با `django.core.signing` است
|
||
(`accounts/authentication.py`). امضا با `DJANGO_SECRET_KEY` انجام میشود. روی سرور این
|
||
مقدار هنوز همان fallbackِ `django-insecure-…` است که در `config/settings.py` کامیت شده
|
||
(آخرین تأیید: ۲۰۲۶-۰۹-۲۴، بهصورتِ بولین).
|
||
- **خطر:** هر کس ریپو را ببیند (Gitea، zipِ تحویلی، هر کپی) میتواند برای **هر `user_id`**
|
||
توکنِ معتبر بسازد و بهجای آن کاربر وارد شود؛ از جمله خریدِ اشتراک، دیدنِ گفتگوها و
|
||
دادهی خانواده.
|
||
- **چرا باز مانده:** عوض کردنش همهی کاربرهای واردشده (توسعهدهندهها، فرانتکار، حسابهای
|
||
دمو در حالِ ارائه) را بیرون میاندازد. تصمیمِ صاحبِ پروژه: بعد از ارائه.
|
||
- **رفع:** `DJANGO_SECRET_KEY` را از `.env` **و** از یونیتِ systemd حذف کنید تا
|
||
`config/settings.py` یک کلیدِ تصادفی بسازد و در `.django_secret_key` (مجوز `0600`) نگه
|
||
دارد. سرویس را ریاستارت کنید. همه یک بار دوباره وارد میشوند.
|
||
- **چطور بسنجید:** `manage.py check --deploy` نباید `security.W009` بدهد؛ روی سرور
|
||
`venv/bin/python -c "from django.conf import settings; print(settings.SECRET_KEY.startswith('django-insecure'))"`
|
||
باید `False` چاپ کند (خودِ کلید را چاپ نکنید).
|
||
|
||
## ۲. حسابهای دمو با کدِ ورودِ ثابت (`TEST_LOGIN_PHONES`)
|
||
|
||
- **چیست:** ۷ شمارهی `09120000101` تا `09120000107` با کدِ ثابت در `.env` سرور. شمارهها
|
||
در ریپو (`docs/operations.md`، `CLAUDE.md`) آمدهاند؛ کدها فقط در راهنمای ارائهی صاحبِ
|
||
پروژهاند.
|
||
- **خطر:** کسی که کد را از راهنما، اسکرینشات یا ارائه ببیند وارد آن حسابها میشود. این
|
||
کدها با `OTP_MAX_FAILED_ATTEMPTS` باطل نمیشوند (ردیفِ `OTPCode` ندارند)؛ فقط سقفِ
|
||
per-workerِ تلاشِ ورود جلویشان است.
|
||
- **رفع:** `TEST_LOGIN_PHONES` را از `.env` سرور پاک کنید و ریاستارت کنید.
|
||
- **چطور بسنجید:** `/api/health` دیگر هشدارِ «شمارههای تستِ ورود با کدِ ثابت فعالاند» ندارد.
|
||
|
||
## ۳. دادهی دمو و مهمانِ عمومی
|
||
|
||
- **افزوده ۲۰۲۶-۰۹-۲۵:** کاربرِ ۱۷ (`09000000001`، «کاربر تست»، `deviceId =
|
||
deploy-check-20260925`) از تستِ دیپلویِ پیامک، و کاربرِ ۱۸ (مهمان، `deploy-check-ai-20260925`)
|
||
از تستِ دستیار؛ همراهِ بقیه پاک شوند.
|
||
- **چیست:** کاربرهای ۶ (`deviceId = frontend-test-device`) و ۱۰ تا ۱۵، با درس، گفتگو،
|
||
تیکت، خانواده و زیرمجموعه، روی دیتابیسِ پروداکشن ساخته شدهاند. `frontend-test-device`
|
||
در ریپو نوشته شده؛ هر کس `POST /api/auth/session` با همین `deviceId` بزند، توکنِ همان
|
||
حساب را میگیرد.
|
||
- **خطر:** دادهی ساختگی در آمار و جدولِ رتبهبندیِ واقعی؛ یک حسابِ عمومی که هر کس
|
||
میتواند با آن کار کند (ثبتِ تیکت، گفتگو با AI به هزینهی ما).
|
||
- **رفع:** پیش از کاربرِ واقعی پاک شوند. `purge_demo_data` اینها را **نمیگیرد** (از مسیرِ
|
||
واقعیِ viewها ساخته شدهاند)؛ باید با شناسه پاک شوند، پیش از آن بکاپ.
|
||
- **چطور بسنجید:** `POST /api/auth/session` با `frontend-test-device` باید `isNewUser: true` بدهد.
|
||
|
||
## ۴. `/api/health` عمومی و پرجزئیات
|
||
|
||
- **چیست:** `config/health.py`، `AllowAny`. بدونِ توکن برمیگرداند: `debug`، `warnings`
|
||
(مثلاً «شمارههای تست فعالاند»، «محدودیتِ نرخ بینِ workerها مشترک نیست»)، `integrations`
|
||
(کدام سرویسها پیکربندیاند، وضعیتِ اعتبارِ AI، نامِ مدل)، `traffic` (تعدادِ کاربر، فعالِ
|
||
۲۴ ساعتِ اخیر، زمانِ آخرین فعالیت)، `allowedOrigins`، `clientIp`، شمارِ محتوا و مهاجرتها.
|
||
- **خطر:** نقشهی حمله: مهاجم میفهمد محدودیتها سهبرابر دورزدنیاند، حسابِ کدِ ثابت
|
||
هست، اعتبارِ AI کی تمام شده، و ترافیکِ واقعی چقدر است.
|
||
- **چرا باز مانده:** صاحبِ پروژه و agentها بدونِ دسترسیِ SSH از همین صفحه سلامتِ سرور را
|
||
میبینند.
|
||
- **رفع:** پاسخِ عمومی فقط `{"status": "ok"|"degraded"}`؛ بقیه پشتِ هدرِ
|
||
`X-Internal-Key` (`INTERNAL_SERVICE_KEY`) یا فقط برای staff.
|
||
- **چطور بسنجید:** `curl https://mathapi.tadrisino2.ir/api/health` بدونِ هدر نباید
|
||
`warnings` و `traffic` داشته باشد.
|
||
|
||
## ۵. مستنداتِ API عمومی
|
||
|
||
- **چیست:** `/api/docs/` (Swagger)، `/api/redoc/` و `/api/schema/` بدونِ ورود بازاند
|
||
(تأیید: HTTP 200). schema همهی endpointها، پارامترها، کدهای خطا و هدرهای داخلی
|
||
(`X-Internal-Key`) را فهرست میکند.
|
||
- **خطر:** شناساییِ کاملِ سطحِ حمله بدونِ زحمت.
|
||
- **چرا باز مانده:** قراردادِ مشترک با فرانت همینجاست؛ فرانتکار و ارائه به آن نیاز دارند.
|
||
- **رفع:** در پروداکشن `SERVE_PERMISSIONS` در `SPECTACULAR_SETTINGS` را به
|
||
`IsAdminUser` (staffِ جنگو) محدود کنید، یا مسیر را در nginx به IPهای تیم ببندید.
|
||
- **چطور بسنجید:** `curl -o /dev/null -w "%{http_code}" …/api/schema/` باید ۴۰۱/۴۰۳ بدهد.
|
||
|
||
## ۶. جدولِ رتبهبندیِ عمومی — حریمِ خصوصیِ کودک
|
||
|
||
- **چیست:** `GET /api/leaderboard` بدونِ توکن (تأیید: HTTP 200) برای هر ردیف `rank`،
|
||
`userId`، `name` و `xpInPeriod` میدهد.
|
||
- **خطر:** نامِ کودکان و شناسهی داخلیشان برای همهی اینترنت قابلِ جمعآوری است.
|
||
پاکسازیِ نام (حذفِ شماره و لینک، `clean_display_name`) فقط راهِ تماس را میگیرد، نه خودِ نام را.
|
||
- **رفع:** `IsAuthenticated`؛ حذفِ `userId` از پاسخ (جز برای ردیفِ خودِ کاربر)؛ نمایشِ
|
||
نامِ کوچک یا نامِ مستعار.
|
||
|
||
## ۷. پنلِ ادمین روی دامنهی عمومی
|
||
|
||
- **چیست:** `/admin/` روی `mathapi.tadrisino2.ir`، با ورودِ نامکاربری/رمزِ جنگو. از
|
||
۲۰۲۶-۰۹-۲۵ سقفِ ۱۰ تلاش در ۱۵ دقیقه برای هر IP دارد (`AdminLoginThrottleMiddleware`)،
|
||
ولی: شمارش per-worker است (مورد ۹)، IP با XFF جعلپذیر است (مورد ۸)، و ۲FA ندارد.
|
||
- **خطر:** ادمین همهی محتوا، پلنها، کدهای تخفیف، سفارشها و کاربرها را کنترل میکند.
|
||
- **رفع:** محدود کردنِ `/admin/` در nginx به IPهای تیم یا VPN؛ تغییرِ مسیرِ پیشفرض؛ ۲FA؛
|
||
رمزهای قوی و حسابِ staffِ جدا برای هر نفر.
|
||
|
||
## ۸. دسترسیِ مستقیم به پورتِ ۸۰ (دور زدنِ ArvanCloud)
|
||
|
||
- **چیست:** `http://78.38.35.249/api/health` با `Host: mathapi.tadrisino2.ir` پاسخ میدهد
|
||
(تأیید: HTTP 200). `NUM_PROXIES=2` فرض میکند همیشه دو پروکسی جلوی ماست.
|
||
- **خطر:** کسی که IP را بداند `X-Forwarded-For` را جعل میکند و محدودیتهای IP-محور
|
||
(مهمان، OTP، ادمین) را دور میزند.
|
||
- **رفع (ادمینِ سرور):** پورتِ ۸۰/۴۴۳ فقط برای رنجهای ArvanCloud، یا `set_real_ip_from` +
|
||
`real_ip_header` در nginx.
|
||
|
||
## ۹. نبودِ کشِ مشترک (`CACHE_URL`)
|
||
|
||
- **چیست:** بدونِ `CACHE_URL` هر worker کشِ `LocMemCache` خودش را دارد (۳ worker).
|
||
- **خطر:** همهی محدودیتهای مبتنی بر کش تا سه برابر شلترند: throttleهای DRF، cooldown و
|
||
سقفِ درخواستِ OTP، سقفِ تلاشِ ورودِ OTP بهازای شماره، سقفِ ورودِ ادمین؛ و باطلشدنِ کش
|
||
بعد از تغییرِ ادمین فقط در یک worker. (سقفِ حدسِ هر کدِ OTP در دیتابیس است و متأثر نیست.)
|
||
- **رفع:** `CACHE_URL=redis://127.0.0.1:6379/1` (db0 مالِ اپِ دیگری است) و ریاستارت.
|
||
- **چطور بسنجید:** `/api/health` → `checks.cache.backend` نباید `LocMemCache` باشد.
|
||
|
||
## ۱۰. ورودِ مهمان فقط با `deviceId`
|
||
|
||
- **چیست:** `POST /api/auth/session` با هر `deviceId` یا حسابِ مهمانِ همان دستگاه را
|
||
برمیگرداند یا میسازد (`accounts/views.py::GuestSessionView`). هیچ رازِ دیگری در کار نیست.
|
||
- **خطر:** هر کس `deviceId`ِ یک مهمان را بداند (از `localStorage`، لاگ، اسکرینشات) وارد
|
||
حسابِ او میشود. بعد از ثبتنام با شماره این راه بسته میشود (مهمان دیگر مهمان نیست).
|
||
- **رفع:** برای مهمان هم یک رازِ تصادفیِ سمتِ سرور صادر شود و ورودِ دوباره با همان راز باشد،
|
||
نه با `deviceId`ِ خام.
|
||
|
||
## ۱۱. دادهی حساس بیهش در دیتابیس
|
||
|
||
- **چیست:** `Session.refresh_token` و `OTPCode.code` بهصورتِ متنِ ساده ذخیره میشوند
|
||
(`accounts/models.py`) و در پنلِ ادمین (`Session`, `OTPCode`) دیده میشوند.
|
||
- **خطر:** هر کس بکاپِ دیتابیس یا پنلِ ادمین را ببیند نشستِ هر کاربر را میرباید.
|
||
بکاپها در `.deploy-backups/` کنارِ خودِ پروژه روی یک ماشینِ مشترکاند.
|
||
- **رفع:** ذخیرهی هشِ SHA-256 و مقایسه با هش؛ حذفِ این دو فیلد از نمایشِ ادمین.
|
||
|
||
## ۱۲. هزینهی دستیارِ AI
|
||
|
||
- **چیست:** سقفِ روزانه ۱۵ سؤال **برای هر کاربر** (`DAILY_AI_QUOTA`) و ۲۰ در ساعت؛ مهمانها
|
||
بر اساسِ IP محدود میشوند ولی هر IP ساعتی ۲۰ حسابِ مهمانِ تازه میسازد (`guest_session`)،
|
||
و هر حسابِ تازه ۱۵ سؤالِ تازه دارد. طولِ سؤال از ۲۰۲۶-۰۹-۲۵ حداکثر ۲۰۰۰ نویسه است.
|
||
- **خطر:** با چند IP، اعتبارِ GapGPT در چند ساعت تمام میشود (اعتبار یک بار در ۲۰۲۶-۰۹-۲۵
|
||
به صفر رسید، البته به دلیلِ دیگر).
|
||
- **رفع:** سقفِ سراسریِ روزانه برای کلِ اپ؛ دستیار فقط برای کاربرِ واردشده با شماره یا
|
||
با سقفِ بسیار کمتر برای مهمان؛ هشدار وقتی `aiProvider.state` در `/api/health` برابرِ
|
||
`quota_exhausted` شد.
|
||
|
||
## ۱۳. محدودیتِ قلب خاموش است
|
||
|
||
- **چیست:** با صفر قلب، سرور همچنان جواب را میپذیرد، مگر `HEARTS_ENFORCED=1`
|
||
(`content/views.py::QuestionAnswerView`). «قلبِ نامحدود» تنها مزیتِ اشتراک است.
|
||
- **خطر:** مزیتِ پولی فقط در ظاهرِ اپ اعمال میشود؛ با صدا زدنِ مستقیمِ API دور زده میشود.
|
||
- **چرا باز مانده:** فرانت برای ۴۰۳ِ `no_hearts` هنوز صفحه ندارد؛ روشن کردنش دانشآموز را به
|
||
صفحهی خطا میبرد. رفتار با صفر قلب هم تصمیمِ محصولیِ باز است.
|
||
- **رفع:** بعد از صفحهی «قلبها تمام شد» در فرانت، `HEARTS_ENFORCED=1` در `.env`.
|
||
|
||
## ۱۴. دیتابیس و بکاپ
|
||
|
||
- **چیست:** SQLite در `~/mathapp-v2/db.sqlite3`، روی دیسکی که بینِ چند سرویس مشترک است و
|
||
~۸۳٪ پر است. `transaction_mode: IMMEDIATE` همزمانی را درست ولی سریالی میکند. بکاپ دستی
|
||
است (`deploy.sh` پیش از `migrate` بکاپ نمیگیرد) و در همان پوشه میماند.
|
||
- **خطر:** از دست رفتنِ کلِ داده با یک خطای دیسک یا یک دستورِ اشتباه؛ پُر شدنِ دیسک
|
||
سرویسهای دیگر (Gitea، ایمیل) را هم میخواباند.
|
||
- **رفع:** مهاجرت به Postgres (روی همان ماشین نصب است)، بکاپِ خودکار پیش از هر `migrate`، و
|
||
نگهداریِ بکاپ بیرون از ماشین.
|
||
|
||
## ۱۵. دسترسیِ سرور
|
||
|
||
- **چیست:** ورود با رمز به حسابِ مشترکِ `administrator` با `sudo (ALL : ALL) ALL` و عضوِ
|
||
`docker` و `lxd`، روی ماشینی که Gitea و ایمیلِ شرکت هم رویش است. رمز یک بار در گفتگو با
|
||
agent گذاشته شده و روی دسکتاپِ صاحبِ پروژه در یک فایلِ متنی نگه داشته میشود.
|
||
- **خطر:** هر نشتِ رمز یعنی دسترسیِ root به کلِ ماشینِ شرکت، نه فقط این پروژه.
|
||
- **رفع (ادمینِ سرور):** کلیدِ SSH بهجای رمز، تعویضِ رمز، حسابِ جدا برای این پروژه با
|
||
دسترسیِ فقط به `~/mathapp-v2` و یک خطِ `NOPASSWD` فقط برای
|
||
`systemctl restart zareian-mathapp`؛ پاک کردنِ فایلِ رمز از دسکتاپ (Keychain جایگزین است).
|
||
|
||
## ۱۶. نگهداریِ توکن در فرانت
|
||
|
||
- **چیست:** کلاینتِ فرانت (`src/api/index.ts` در مخزنِ فرانت) توکن و refresh را در
|
||
`localStorage` نگه میدارد.
|
||
- **خطر:** هر 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…` باید دومی ۴۲۹ بگیرد.
|
||
- **✅ بسته شد ۲۰۲۶-۰۹-۲۵** (همان روزی که کلیدِ کاوهنگار روی سرور رفت؛ کامیت `0580aec`،
|
||
روی سرور از ۱۲:۳۲). `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` بگذارید و
|
||
همانجا بسنجید؛ یا شناسهی غیرترتیبی.
|
||
|
||
---
|
||
|
||
## چیزهایی که بسته شدهاند (برای اینکه دوباره باز نشوند)
|
||
|
||
در بازبینیِ ۲۰۲۶-۰۹-۲۵ (کامیتِ `42783f9`) بسته شدند؛ جزئیات در `docs/architecture.md`:
|
||
پلنِ هر خرید روی سفارش، دست نخوردنِ اشتراکِ فعال هنگامِ خرید، یک کدِ تخفیف پیش از درگاه،
|
||
حذفِ `/api/payment/webhook`، امتیاز فقط از جوابِ ثبتشده، OTPِ ۶ رقمی با ۵ حدس، خروج از حساب،
|
||
حفظِ مهمان هنگامِ ثبتنام، `select_for_update` مؤثر با `IMMEDIATE`، سقفِ ورودِ ادمین، سقفِ
|
||
طولِ سؤالِ AI، مقایسهی زمانثابتِ `X-Internal-Key`.
|
||
|
||
هر تغییری در این مسیرها باید تستهای `payment/tests_audit_fixes.py` و
|
||
`accounts/tests_audit_fixes.py` را سبز نگه دارد.
|