Signed payment links, hashed refresh tokens, login-only leaderboard, load test tools
- /api/payment/orders/{id}/pay requires the short-lived signature that paymentUrl carries;
without it (or with another order's) it is the same 404 as an unknown order
- Session.refresh_token stores the SHA-256 of the token; migration accounts.0007 hashes the
existing rows so tokens clients already hold keep working
- /api/leaderboard requires a token (guests have one); names of children were scrapable
- RAG mock only for `manage.py test`, not any command with an argument named "test"
- tools/loadtest: user factory, load generator, fake GapGPT+Qdrant, with method notes
- security-before-production.md lists only open items; closed ones become a guards table
with the tests that hold them. Comments describe current behaviour, not history
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
# پیش از پروداکشنِ واقعی: چیزهایی که عمداً باز ماندهاند
|
||||
|
||||
**تاریخِ ثبت: ۲۰۲۶-۰۹-۲۵ (۳ مهر ۱۴۰۵)، کامیتِ پایه `66066cb`.** بازبینیِ دوباره با سرورِ زنده:
|
||||
۲۰۲۶-۱۰-۰۴ (مواردِ ۱۹ تا ۲۳ و بازتولیدِ مورد ۸).
|
||||
**آخرین بازبینی با کد و سرورِ زنده: ۲۰۲۶-۱۰-۰۴ (۱۲ مهر ۱۴۰۵).**
|
||||
|
||||
این پروژه تا این تاریخ در حالتِ **دمو و ارائهی کارآموزی** است، نه محصولِ نهایی. چند چیز
|
||||
عمداً باز گذاشته شده تا توسعهدهندهها، فرانتکار و ارائه کار کنند. هر کدام برای یک سرورِ
|
||||
@@ -10,8 +9,9 @@
|
||||
|
||||
**برای agentها و توسعهدهندههای بعدی:** این فهرست ادعاست، نه ضمانت؛ ممکن است از این
|
||||
تاریخ به بعد چیزی عوض شده باشد. پیش از بستنِ هر مورد، وضعیتِ فعلیاش را با خودِ سرور یا
|
||||
کد بسنجید (ستونِ «چطور بسنجید»). بعد از بستنِ هر مورد، همینجا تاریخ و کامیتِ رفعش را
|
||||
بنویسید؛ مورد را پاک نکنید.
|
||||
کد بسنجید (ستونِ «چطور بسنجید»). مورد که بسته شد، از فهرست برداشته میشود و یک خط در
|
||||
«حفاظهایی که هست» (پایینِ همین فایل) میگیرد، با نامِ تستی که آن را نگه میدارد. تاریخ و
|
||||
دلیلِ تغییر در پیامِ کامیت است، نه اینجا.
|
||||
|
||||
هر آزمونِ «از بیرون» با `curl` بدونِ توکن انجام شده و در تاریخِ بالا تأیید شده است.
|
||||
|
||||
@@ -26,22 +26,16 @@
|
||||
| ۳ | دادهی دمو و حسابِ مهمانِ عمومیِ `frontend-test-device` | بالا | دیتابیسِ سرور |
|
||||
| ۴ | `/api/health` جزئیاتِ داخلی را به همه نشان میدهد | متوسط | کد |
|
||||
| ۵ | Swagger، ReDoc و schemaی کاملِ API عمومیاند | متوسط | کد |
|
||||
| ۶ | جدولِ رتبهبندی نام و شناسهی کودکان را بدونِ ورود نشان میدهد | متوسط (حریمِ خصوصیِ کودک) | کد |
|
||||
| ۷ | پنلِ `/admin/` روی دامنهی عمومی | متوسط | nginx + کد |
|
||||
| ۸ | سرور بدونِ ArvanCloud روی پورتِ ۸۰ هم جواب میدهد | متوسط | ادمینِ سرور |
|
||||
| ۹ | کشِ مشترک (Redis) نیست؛ محدودیتها per-workerاند | متوسط | `.env` سرور |
|
||||
| ۱۰ | ورودِ مهمان فقط با `deviceId` است | کم (طراحی) | کد |
|
||||
| ۱۱ | توکنِ refresh و کدِ OTP بیهش در دیتابیس | کم | کد |
|
||||
| ۱۱ | کدِ OTP بیهش در دیتابیس و در فهرستِ پنل | کم | کد |
|
||||
| ۱۲ | هزینهی دستیارِ AI با ساختِ حسابِ مهمان قابلِ بالا بردن است | کم (مالی) | کد |
|
||||
| ۱۳ | ~~محدودیتِ قلب در سرور خاموش است (`HEARTS_ENFORCED=0`)~~ **بسته شد ۲۰۲۶-۱۰-۰۲** | کم (کسبوکار) | `.env` + فرانت |
|
||||
| ۱۴ | SQLite روی دیسکِ مشترکِ پُر، بدونِ بکاپِ خودکارِ بیرون از ماشین | کم (داده) | زیرساخت |
|
||||
| ۱۵ | دسترسیِ سرور: حسابِ مشترک، رمز، sudoِ کامل | بالا | ادمینِ سرور |
|
||||
| ۱۶ | فرانت توکن را در `localStorage` نگه میدارد | کم | فرانت |
|
||||
| ۱۷ | ~~شمارهی موبایل نرمال نمیشود؛ سقفهای per-phone با املای دیگر دور زده میشوند~~ **بسته شد ۲۰۲۶-۰۹-۲۵** | متوسط | کد |
|
||||
| ۱۸ | `/api/payment/orders/{id}/pay` بدونِ ورود و بدونِ سنجشِ مالکیت | کم | کد |
|
||||
| ۱۹ | دیتابیس و بکاپها برای همهی کاربرانِ ماشینِ مشترک خواندنی بودند | بالا | کد آماده (`deploy.sh`)؛ اعمال با دیپلوی |
|
||||
| ۲۰ | یونیتِ systemd هنوز `DJANGO_DEBUG=1` و کلیدِ ناامن دارد؛ فقط یک خطِ `.env` جلوی DEBUG را گرفته | بالا (نهفته) | ادمینِ سرور |
|
||||
| ۲۱ | `urllib3 2.7.0` و `python-dotenv 1.0.1` آسیبپذیریِ شناختهشده داشتند | کم | کد آماده؛ اعمال با دیپلوی |
|
||||
| ۲۰ | یونیتِ systemd `DJANGO_DEBUG=1` و کلیدِ ناامن دارد؛ فقط یک خطِ `.env` جلوی DEBUG را گرفته | بالا (نهفته) | ادمینِ سرور |
|
||||
| ۲۲ | سایتِ فرانت هیچ هدرِ امنیتی ندارد (CSP، frame-ancestors، HSTS) | کم | nginx/فرانت |
|
||||
| ۲۳ | سقفِ دستیار برای مهمانها بر اساسِ IP مشترک است؛ کلاسِ پشتِ یک IP بعد از ۲۰ سوال در ساعت ۴۲۹ میگیرد | متوسط (دسترسپذیری) | تصمیمِ محصولی |
|
||||
|
||||
@@ -117,19 +111,9 @@
|
||||
`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`)،
|
||||
- **چیست:** `/admin/` روی `mathapi.tadrisino2.ir`، با ورودِ نامکاربری/رمزِ جنگو. سقفِ ۱۰ تلاش در ۱۵ دقیقه برای هر IP دارد (`AdminLoginThrottleMiddleware`)،
|
||||
ولی: شمارش per-worker است (مورد ۹)، IP با XFF جعلپذیر است (مورد ۸)، و ۲FA ندارد.
|
||||
- **خطر:** ادمین همهی محتوا، پلنها، کدهای تخفیف، سفارشها و کاربرها را کنترل میکند.
|
||||
- **رفع:** محدود کردنِ `/admin/` در nginx به IPهای تیم یا VPN؛ تغییرِ مسیرِ پیشفرض؛ ۲FA؛
|
||||
@@ -168,38 +152,26 @@
|
||||
- **رفع:** برای مهمان هم یک رازِ تصادفیِ سمتِ سرور صادر شود و ورودِ دوباره با همان راز باشد،
|
||||
نه با `deviceId`ِ خام.
|
||||
|
||||
## ۱۱. دادهی حساس بیهش در دیتابیس
|
||||
## ۱۱. کدِ OTP بیهش در دیتابیس
|
||||
|
||||
- **چیست:** `Session.refresh_token` و `OTPCode.code` بهصورتِ متنِ ساده ذخیره میشوند
|
||||
(`accounts/models.py`) و در پنلِ ادمین (`Session`, `OTPCode`) دیده میشوند.
|
||||
- **خطر:** هر کس بکاپِ دیتابیس یا پنلِ ادمین را ببیند نشستِ هر کاربر را میرباید.
|
||||
بکاپها در `.deploy-backups/` کنارِ خودِ پروژه روی یک ماشینِ مشترکاند.
|
||||
- **رفع:** ذخیرهی هشِ SHA-256 و مقایسه با هش؛ حذفِ این دو فیلد از نمایشِ ادمین.
|
||||
- **چیست:** `OTPCode.code` متنِ ساده است و در فهرستِ پنلِ ادمین (`OTPCodeAdmin`) دیده میشود.
|
||||
توکنِ تمدید (`Session.refresh_token`) هششده است و اینجا شامل نیست.
|
||||
- **چرا باز مانده:** تیم برای آزمایش بیپیامک یک ردیفِ `OTPCode` در پنل میسازد و کدش را میبیند.
|
||||
- **خطر:** کسی که پنل یا دیتابیس را ببیند، در چند دقیقهی عمرِ هر کد میتواند با آن وارد شود.
|
||||
- **رفع:** ذخیرهی هش و حذفِ ستون از فهرستِ پنل، همراه با راهِ دیگری برای ورودِ آزمایشی.
|
||||
|
||||
## ۱۲. هزینهی دستیارِ AI
|
||||
|
||||
- **چیست:** از ۲۰۲۶-۱۰-۰۲ هر کاربر روزی ۳ سؤالِ رایگان دارد (`AI_DAILY_QUOTA_FREE`؛ پیشتر ۱۵)
|
||||
- **چیست:** هر کاربر روزی ۳ سؤالِ رایگان دارد (`AI_DAILY_QUOTA_FREE`)
|
||||
و بعد فقط با اعتبارِ خریدهشده؛ ۲۰ در ساعت. مهمانها بر اساسِ IP محدود میشوند ولی هر IP ساعتی
|
||||
۲۰ حسابِ مهمانِ تازه میسازد (`guest_session`)، و هر حسابِ تازه ۳ سؤالِ رایگانِ تازه دارد. خطر
|
||||
کمتر شد (۳ بهجای ۱۵)، ولی بسته نیست. طولِ سؤال از ۲۰۲۶-۰۹-۲۵ حداکثر ۲۰۰۰ نویسه است.
|
||||
۶۰ حسابِ مهمانِ تازه میسازد (`guest_session`)، و هر حسابِ تازه ۳ سؤالِ رایگانِ تازه دارد. سقفِ
|
||||
روزانهی کلِ سامانه (`AI_GLOBAL_DAILY_MAX`، ۳۰۰۰) خرجِ کل را محدود میکند. طولِ سؤال حداکثر ۲۰۰۰ نویسه است.
|
||||
- **خطر:** با چند IP، اعتبارِ GapGPT در چند ساعت تمام میشود (اعتبار یک بار در ۲۰۲۶-۰۹-۲۵
|
||||
به صفر رسید، البته به دلیلِ دیگر).
|
||||
- **رفع:** سقفِ سراسریِ روزانه برای کلِ اپ؛ دستیار فقط برای کاربرِ واردشده با شماره یا
|
||||
با سقفِ بسیار کمتر برای مهمان؛ هشدار وقتی `aiProvider.state` در `/api/health` برابرِ
|
||||
`quota_exhausted` شد.
|
||||
|
||||
## ۱۳. ~~محدودیتِ قلب خاموش است~~ — بسته شد ۲۰۲۶-۱۰-۰۲
|
||||
|
||||
- **چیست:** با صفر قلب، سرور همچنان جواب را میپذیرد، مگر `HEARTS_ENFORCED=1`
|
||||
(`content/views.py::QuestionAnswerView`). «قلبِ نامحدود» تنها مزیتِ اشتراک است.
|
||||
- **خطر:** مزیتِ پولی فقط در ظاهرِ اپ اعمال میشود؛ با صدا زدنِ مستقیمِ API دور زده میشود.
|
||||
- **چرا باز مانده:** فرانت برای ۴۰۳ِ `no_hearts` هنوز صفحه ندارد؛ روشن کردنش دانشآموز را به
|
||||
صفحهی خطا میبرد. رفتار با صفر قلب هم تصمیمِ محصولیِ باز است.
|
||||
- **رفع:** بعد از صفحهی «قلبها تمام شد» در فرانت، `HEARTS_ENFORCED=1` در `.env`.
|
||||
- **بسته شد ۲۰۲۶-۱۰-۰۲** (بکاند `4700ccb`، فرانت `33f5f8d`): `HEARTS_ENFORCED` پیشفرض روشن؛ چکِ
|
||||
قلب داخلِ تراکنش؛ پایانِ درس/جزیرهی بیجواب با قلبِ صفر هم بسته؛ صفحهی «قلبهات تموم شد» با
|
||||
شمارشِ معکوس. تستها: `content/tests_hearts_gate.py`.
|
||||
|
||||
## ۱۴. دیتابیس و بکاپ
|
||||
|
||||
- **چیست:** SQLite در `~/mathapp-v2/db.sqlite3`، روی دیسکی که بینِ چند سرویس مشترک است و
|
||||
@@ -229,58 +201,7 @@
|
||||
- **خطر:** هر 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` بگذارید و
|
||||
همانجا بسنجید؛ یا شناسهی غیرترتیبی.
|
||||
|
||||
## ۱۹. دیتابیس و بکاپها برای همهی کاربرانِ ماشین خواندنی بودند (۲۰۲۶-۱۰-۰۴)
|
||||
|
||||
- **چیست:** `/home/administrator` با مجوزِ 755، `~/mathapp-v2` و `.deploy-backups` با 775، و
|
||||
`db.sqlite3` و هر ۳۵ بکاپ با 644. `.env` درست 600 بود.
|
||||
- **بازتولید شد:** `sudo -u nobody head -c 15 ~/mathapp-v2/db.sqlite3` و همین برای یک بکاپ، هر دو
|
||||
`SQLite format 3` چاپ کردند. هر حسابِ دیگرِ این ماشین (aria، artina، behdad، zahra، git، gitea،
|
||||
metabase) یا هر سرویسِ نفوذشدهی دیگری شمارهها، گفتگوهای کودکان و refresh tokenهای بیهش (مورد ۱۱،
|
||||
یعنی ورود بهجای هر کاربر) را میخواند.
|
||||
- **رفع در کد:** مرحلهی «مجوزِ فایلهای داده» در `deploy.sh`: پوشهی پروژه بیدسترسیِ others،
|
||||
دیتابیس 600، پوشهی بکاپ 700 و فایلهایش 600، در هر انتشار (بکاپِ دستیِ تازه دوباره 644 ساخته
|
||||
میشود). خودِ `/home/administrator` دست نخورد چون nginx فرانت را از `~/mathapp_frontend_new/dist`
|
||||
میخواند. بعد از دیپلوی باید دوباره با `sudo -u nobody` سنجیده شود. هش کردنِ refresh token (مورد ۱۱)
|
||||
هنوز باز است.
|
||||
|
||||
## ۲۰. یونیتِ systemd: DEBUG و کلیدِ ناامن (۲۰۲۶-۱۰-۰۴)
|
||||
## ۲۰. یونیتِ systemd: DEBUG و کلیدِ ناامن
|
||||
|
||||
- **چیست:** `systemctl cat gunicorn-mathapp` هنوز `Environment="DJANGO_DEBUG=1"` و
|
||||
`Environment="DJANGO_SECRET_KEY=django-insecure-…"` دارد. `EnvironmentFile=.env` بعد از آن میآید و
|
||||
@@ -292,17 +213,7 @@
|
||||
ریاستارت. حذفِ خطِ کلید بهتنهایی کسی را بیرون نمیاندازد اگر `.env` همان کلید را داشته باشد؛
|
||||
عوض کردنِ خودِ کلید (مورد ۱) جداست.
|
||||
|
||||
## ۲۱. کتابخانههای آسیبپذیر (۲۰۲۶-۱۰-۰۴)
|
||||
|
||||
- **چیست:** پایگاهِ OSV برای ۴۸ بستهی نصبشده: `urllib3 2.7.0` (GHSA-8988-9cw3-xx77 و
|
||||
GHSA-vxq7-64xx-v4gw با شدتِ بالا، GHSA-gh4c-6fx4-qh6g متوسط) و `python-dotenv 1.0.1`
|
||||
(GHSA-mf9w-mj56-hr94، فقط در `set_key` که پروژه صدا نمیزند). اثرِ عملی کم است: سناریوی هر سهی
|
||||
urllib3 پاسخِ مخربِ سرورِ بالادستی یا پراکسی است (بازار، زرینپال، کاوهنگار، Qdrant). `npm audit` روی
|
||||
وابستگیهای اجراییِ فرانت صفر.
|
||||
- **رفع در کد:** `python-dotenv==1.2.2`، `urllib3>=2.8.0`، و `gunicorn==26.2.0` (تا امروز فقط دستی
|
||||
روی سرور نصب بود). هر سه با پایتونِ ۳.۱۰ِ سرور سازگارند و سرور به PyPI دسترسی دارد (سنجیده شد).
|
||||
|
||||
## ۲۲. هدرهای امنیتیِ فرانت (۲۰۲۶-۱۰-۰۴)
|
||||
## ۲۲. هدرهای امنیتیِ فرانت
|
||||
|
||||
- **چیست:** `https://math.tadrisino2.ir/` فقط `x-xss-protection` دارد؛ نه CSP، نه `X-Frame-Options`/
|
||||
`frame-ancestors`، نه HSTS، نه `X-Content-Type-Options`. API همهی اینها را جز HSTS و CSP دارد.
|
||||
@@ -312,26 +223,33 @@
|
||||
- **رفع:** در nginxِ `mathapp-front` یا در آروان: `Content-Security-Policy` با `frame-ancestors 'none'`
|
||||
و `connect-src` به دامنهی API، و `X-Content-Type-Options: nosniff`.
|
||||
|
||||
## ۲۳. سقفِ دستیار برای مهمانها روی IPِ مشترک (۲۰۲۶-۱۰-۰۴)
|
||||
## ۲۳. سقفِ دستیار برای مهمانها روی IPِ مشترک
|
||||
|
||||
- **چیست:** `GuestAwareAIThrottle` برای مهمانها سطلِ `ai_ask` (۲۰ در ساعت) را بر اساسِ IP میسازد تا
|
||||
ساختنِ مهمانِ تازه سقف را دور نزند.
|
||||
- **بازتولید شد:** ۲۲ مهمانِ متفاوت از یک IP، هر کدام اولین سوالش: ۲۰ تا ۲۰۰، ۲۱اُم و ۲۲اُم ۴۲۹.
|
||||
دانشآموزانِ مهمانِ یک کلاس پشتِ وایفایِ مدرسه یا CGNAT با هم ساعتی ۲۰ سوال دارند (روی سرور با ۳
|
||||
workerِ بیRedis تا حدودِ ۶۰).
|
||||
- **چرا تصمیمِ محصولی است:** سقفِ IP جلوی «مهمان بساز، سه سوالِ رایگان بگیر» را میگیرد. از
|
||||
۲۰۲۶-۱۰-۰۳ سقفِ سراسریِ `AI_GLOBAL_DAILY_MAX` (۳۰۰۰ در روز، حدودِ ۶ دلار) هزینه را جدا محدود
|
||||
- **چرا تصمیمِ محصولی است:** سقفِ IP جلوی «مهمان بساز، سه سوالِ رایگان بگیر» را میگیرد.
|
||||
سقفِ سراسریِ `AI_GLOBAL_DAILY_MAX` (۳۰۰۰ در روز، حدودِ ۶ دلار) هزینه را جدا محدود
|
||||
میکند، پس میشود سطل را per-user کرد و فقط ساختِ مهمان را محدود نگه داشت. تا تصمیم، دست نخورد.
|
||||
|
||||
---
|
||||
|
||||
## چیزهایی که بسته شدهاند (برای اینکه دوباره باز نشوند)
|
||||
## حفاظهایی که هست (تا دوباره باز نشوند)
|
||||
|
||||
در بازبینیِ ۲۰۲۶-۰۹-۲۵ (کامیتِ `42783f9`) بسته شدند؛ جزئیات در `docs/architecture.md`:
|
||||
پلنِ هر خرید روی سفارش، دست نخوردنِ اشتراکِ فعال هنگامِ خرید، یک کدِ تخفیف پیش از درگاه،
|
||||
حذفِ `/api/payment/webhook`، امتیاز فقط از جوابِ ثبتشده، OTPِ ۶ رقمی با ۵ حدس، خروج از حساب،
|
||||
حفظِ مهمان هنگامِ ثبتنام، `select_for_update` مؤثر با `IMMEDIATE`، سقفِ ورودِ ادمین، سقفِ
|
||||
طولِ سؤالِ AI، مقایسهی زمانثابتِ `X-Internal-Key`.
|
||||
هر تغییری در این مسیرها باید تستهای نامبرده را سبز نگه دارد. جزئیاتِ پرداخت و تخفیف در
|
||||
`docs/architecture.md`.
|
||||
|
||||
هر تغییری در این مسیرها باید تستهای `payment/tests_audit_fixes.py` و
|
||||
`accounts/tests_audit_fixes.py` را سبز نگه دارد.
|
||||
| حفاظ | تست |
|
||||
|---|---|
|
||||
| پلنِ هر خرید روی سفارش؛ خرید اشتراکِ فعال را دست نمیزند؛ یک کدِ تخفیف پیش از درگاه؛ امتیاز فقط از جوابِ ثبتشده | `payment/tests_audit_fixes.py`، `accounts/tests_audit_fixes.py` |
|
||||
| لینکِ پرداخت (`/api/payment/orders/{id}/pay`) فقط با امضای کوتاهعمرِ همراهِ `paymentUrl` باز میشود | `payment/tests.py` (`PaymentRedirectViewTests`) |
|
||||
| توکنِ تمدید فقط بهصورتِ هشِ SHA-256 ذخیره میشود | `accounts/tests.py` (`RefreshTokenStoredHashedTests`) |
|
||||
| جدولِ رتبهبندی فقط برای کاربرِ واردشده | `progress/tests.py` (`LeaderboardPeriodValidationTests`) |
|
||||
| شمارهی موبایل نرمال میشود؛ cooldown، سقفِ روزانهی هر شماره و سقفِ کلِ پیامک در دیتابیس شمرده میشوند | `accounts/tests_otp_limits.py` |
|
||||
| OTPِ ۶ رقمی با ۵ حدس برای هر کد؛ خروج از حساب توکنهای قبلی را باطل میکند | `accounts/tests_audit_fixes.py` |
|
||||
| قلبِ صفر جوابِ سوال و پایانِ درس/جزیره را میبندد؛ چکش داخلِ تراکنش است | `content/tests_hearts_gate.py` |
|
||||
| `select_for_update` مؤثر با `transaction_mode: IMMEDIATE`؛ مقایسهی زمانثابتِ `X-Internal-Key` | `payment/tests_audit_fixes.py` |
|
||||
| دیتابیس و بکاپها فقط برای صاحبِ سرویس خواندنیاند (مرحلهی «مجوزِ فایلهای داده» در `deploy.sh`) | با `sudo -u nobody head -c 15 ~/mathapp-v2/db.sqlite3` روی سرور باید خطای دسترسی بدهد |
|
||||
| کتابخانهها بدونِ آسیبپذیریِ شناختهشده (`urllib3>=2.8.0`، `python-dotenv 1.2.2`) | پایگاهِ OSV روی `pip freeze` |
|
||||
|
||||
Reference in New Issue
Block a user