Remove the guest system entirely: no is_guest/device_id, no guest throttles, phone login is the only way in
accounts.0008 drops the columns and refuses to run while guest rows exist. Docs describe the current state; tests that only proved guest behaviour are gone. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,21 +23,19 @@
|
||||
|---|---|---|---|
|
||||
| ۱ | کلیدِ امضای توکن روی سرور همان رشتهی داخلِ ریپو است | بحرانی — جعلِ ورود بهجای هر کاربر | کد آماده؛ اعمال روی سرور |
|
||||
| ۲ | ۷ حسابِ دمو با کدِ ورودِ ثابت روی پروداکشن | بالا | `.env` سرور |
|
||||
| ۳ | دادهی دمو و حسابِ مهمانِ نمایشیِ `frontend-test-device` | بالا | دیتابیسِ سرور |
|
||||
| ۳ | دادهی دمو روی دیتابیسِ پروداکشن | بالا | دیتابیسِ سرور |
|
||||
| ۴ | `/api/health` جزئیاتِ داخلی را به همه نشان میدهد | متوسط | کد |
|
||||
| ۵ | Swagger، ReDoc و schemaی کاملِ API عمومیاند | متوسط | کد |
|
||||
| ۷ | پنلِ `/admin/` روی دامنهی عمومی | متوسط | nginx + کد |
|
||||
| ۸ | سرور بدونِ ArvanCloud روی پورتِ ۸۰ هم جواب میدهد | متوسط | ادمینِ سرور |
|
||||
| ۹ | کشِ مشترک (Redis) نیست؛ محدودیتها per-workerاند | متوسط | `.env` سرور |
|
||||
| ۱۰ | حسابهای مهمانِ قدیمی (دمو و کاربرانِ پیش از حذفِ ورودِ مهمان) هنوز در دیتابیساند | کم | دیتابیسِ سرور |
|
||||
| ۱۱ | کدِ OTP بیهش در دیتابیس و در فهرستِ پنل | کم | کد |
|
||||
| ۱۲ | هزینهی دستیارِ AI با ساختِ حسابِ تازه (هر شماره ۳ سوالِ رایگان) قابلِ بالا بردن است | کم (مالی) | کد |
|
||||
| ۱۲ | هزینهی دستیارِ AI با ساختِ شمارهی تازه (هر شماره ۳ سوالِ رایگان) قابلِ بالا بردن است | کم (مالی) | کد |
|
||||
| ۱۴ | SQLite روی دیسکِ مشترکِ پُر، بدونِ بکاپِ خودکارِ بیرون از ماشین | کم (داده) | زیرساخت |
|
||||
| ۱۵ | دسترسیِ سرور: حسابِ مشترک، رمز، sudoِ کامل | بالا | ادمینِ سرور |
|
||||
| ۱۶ | فرانت توکن را در `localStorage` نگه میدارد | کم | فرانت |
|
||||
| ۲۰ | یونیتِ systemd `DJANGO_DEBUG=1` و کلیدِ ناامن دارد؛ فقط یک خطِ `.env` جلوی DEBUG را گرفته | بالا (نهفته) | ادمینِ سرور |
|
||||
| ۲۲ | سایتِ فرانت هیچ هدرِ امنیتی ندارد (CSP، frame-ancestors، HSTS) | کم | nginx/فرانت |
|
||||
| ۲۳ | سقفِ دستیار برای مهمانها بر اساسِ IP مشترک است؛ کلاسِ پشتِ یک IP بعد از ۲۰ سوال در ساعت ۴۲۹ میگیرد | متوسط (دسترسپذیری) | تصمیمِ محصولی |
|
||||
| ۲۴ | سقفِ روزانهی ۳۰۰ پیامکِ کلِ سامانه را یک ناشناس پر میکند و ورود با پیامک برای همه بسته میشود | بالا (دسترسپذیری، هزینه) | تصمیمِ طراحی |
|
||||
| ۲۵ | با دانستنِ شمارهی کسی، ورودِ او تا ۲۴ ساعت بسته میشود | متوسط | تصمیمِ طراحی |
|
||||
| ۲۶ | پایانِ درس و جزیره به گفتهی کلاینت است (جزیرهی قفل، قلبِ صفر، گزارشِ والدین) | متوسط | کد، بعد از ارائه |
|
||||
@@ -78,22 +76,14 @@
|
||||
- **رفع:** `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` حذف شده، پس با آن دیگر توکن نمیدهد؛ ولی خودِ
|
||||
ردیفها (با `is_guest=True`) تا پاک نشوند در دیتابیساند.
|
||||
- **خطر:** دادهی ساختگی در آمار و جدولِ رتبهبندیِ واقعی؛ یک حسابِ عمومی که هر کس
|
||||
میتواند با آن کار کند (ثبتِ تیکت، گفتگو با AI به هزینهی ما).
|
||||
- **رفع:** پیش از کاربرِ واقعی پاک شوند. `purge_demo_data` اینها را **نمیگیرد** (از مسیرِ
|
||||
واقعیِ viewها ساخته شدهاند)؛ باید با شناسه پاک شوند، پیش از آن بکاپ. همراهِ آن پیشفرضِ
|
||||
`DEMO_GUEST_DEVICE_IDS` در `config/settings.py` (که حذفِ این مهمان را با `POST /api/user/delete`
|
||||
میبندد) هم برداشته شود.
|
||||
- **چطور بسنجید:** در پنل یا shell، `User.objects.filter(is_guest=True).count()` پس از پاکسازی باید صفر باشد.
|
||||
- **چیست:** کاربرهای ۱۰ تا ۱۵ و ۲۵ (حسابهای دمو با کدِ ثابت، بند ۲) با درس، گفتگو، تیکت و
|
||||
سفارش روی دیتابیسِ پروداکشن ساخته شدهاند. حسابِ ۱۰ («علی رضایی»، با ۷ سفارش) حسابِ نمایشیِ اصلی است.
|
||||
- **خطر:** دادهی ساختگی در آمار و جدولِ رتبهبندیِ واقعی.
|
||||
- **رفع:** پیش از کاربرِ واقعی با `delete_users --ids … --yes` (پیش از آن بکاپ) پاک شوند.
|
||||
`purge_demo_data` اینها را **نمیگیرد** (از مسیرِ واقعیِ viewها ساخته شدهاند).
|
||||
- **چطور بسنجید:** `/api/health` → `traffic.users` فقط کاربرانِ واقعی را بشمارد.
|
||||
|
||||
## ۴. `/api/health` عمومی و پرجزئیات
|
||||
|
||||
@@ -138,13 +128,13 @@
|
||||
- **چیست:** `http://78.38.35.249/api/health` با `Host: mathapi.tadrisino2.ir` پاسخ میدهد
|
||||
(تأیید: HTTP 200). `NUM_PROXIES=2` فرض میکند همیشه دو پروکسی جلوی ماست.
|
||||
- **خطر:** کسی که IP را بداند `X-Forwarded-For` را جعل میکند و محدودیتهای IP-محور
|
||||
(مهمان، OTP، ادمین) را دور میزند.
|
||||
(OTP، ادمین) را دور میزند.
|
||||
- **رفع (ادمینِ سرور):** پورتِ ۸۰/۴۴۳ فقط برای رنجهای ArvanCloud، یا `set_real_ip_from` +
|
||||
`real_ip_header` در nginx.
|
||||
|
||||
- **بازتولید شد ۲۰۲۶-۱۰-۰۴:** `curl -H 'Host: mathapi.tadrisino2.ir' -H 'X-Forwarded-For: 1.2.3.4'
|
||||
http://78.38.35.249/api/health` جواب داد `clientIp: 1.2.3.4`. یعنی هر سقفِ IP-محور (ورودِ ادمین
|
||||
۱۰ در ۱۵ دقیقه، درخواست و ورودِ OTP، ساختِ مهمان، دستیارِ مهمان) با عوض کردنِ این هدر در هر
|
||||
۱۰ در ۱۵ دقیقه، درخواست و ورودِ OTP) با عوض کردنِ این هدر در هر
|
||||
درخواست دور زده میشود، و حفاظتِ DDoS/WAFِ آروان هم برای این مسیر وجود ندارد. سقفهای
|
||||
per-phone و per-code که در دیتابیس شمرده میشوند متأثر نیستند. پورتهای ۸۰ و ۴۴۳ هر دو باز بودند.
|
||||
|
||||
@@ -157,16 +147,6 @@
|
||||
- **رفع:** `CACHE_URL=redis://127.0.0.1:6379/1` (db0 مالِ اپِ دیگری است) و ریاستارت.
|
||||
- **چطور بسنجید:** `/api/health` → `checks.cache.backend` نباید `LocMemCache` باشد.
|
||||
|
||||
## ۱۰. حسابهای مهمانِ قدیمی
|
||||
|
||||
- **چیست:** ورودِ مهمان (`POST /api/auth/session`) حذف شده و هر کاربرِ تازه با شماره ثبتنام میکند.
|
||||
ردیفهای مهمانِ پیشین (`User.is_guest=True`، شامل دموها) در دیتابیس ماندهاند و کدِ ارتقای مهمان به
|
||||
حسابِ شمارهدار (`LoginView`) و throttleهای مهمانمحور هنوز برایشان کار میکنند.
|
||||
- **خطر:** مهمانِ قدیمی هنوز با `refreshToken` ذخیرهشده در مرورگرش وارد میماند؛ شناسهی دستگاه دیگر
|
||||
راهِ ورود نیست.
|
||||
- **رفع:** پس از پاکسازیِ دموها (بند ۳) و دیدنِ اینکه مهمانِ واقعی نمانده، کدِ مهمان
|
||||
(`is_guest`، `GuestAware*Throttle`، ارتقا) حذف شود. تا آن روز عمداً نگه داشته شده.
|
||||
|
||||
## ۱۱. کدِ OTP بیهش در دیتابیس
|
||||
|
||||
- **چیست:** `OTPCode.code` متنِ ساده است و در فهرستِ پنلِ ادمین (`OTPCodeAdmin`) دیده میشود.
|
||||
@@ -238,17 +218,6 @@
|
||||
- **رفع:** در nginxِ `mathapp-front` یا در آروان: `Content-Security-Policy` با `frame-ancestors 'none'`
|
||||
و `connect-src` به دامنهی API، و `X-Content-Type-Options: nosniff`.
|
||||
|
||||
## ۲۳. سقفِ دستیار برای مهمانها روی IPِ مشترک
|
||||
|
||||
- **چیست:** `GuestAwareAIThrottle` برای مهمانها سطلِ `ai_ask` (۲۰ در ساعت) را بر اساسِ IP میسازد تا
|
||||
ساختنِ مهمانِ تازه سقف را دور نزند.
|
||||
- **بازتولید شد:** ۲۲ مهمانِ متفاوت از یک IP، هر کدام اولین سوالش: ۲۰ تا ۲۰۰، ۲۱اُم و ۲۲اُم ۴۲۹.
|
||||
دانشآموزانِ مهمانِ یک کلاس پشتِ وایفایِ مدرسه یا CGNAT با هم ساعتی ۲۰ سوال دارند (روی سرور با ۳
|
||||
workerِ بیRedis تا حدودِ ۶۰).
|
||||
- **چرا تصمیمِ محصولی است:** سقفِ IP جلوی «مهمان بساز، سه سوالِ رایگان بگیر» را میگیرد.
|
||||
سقفِ سراسریِ `AI_GLOBAL_DAILY_MAX` (۳۰۰۰ در روز، حدودِ ۶ دلار) هزینه را جدا محدود
|
||||
میکند، پس میشود سطل را per-user کرد و فقط ساختِ مهمان را محدود نگه داشت. تا تصمیم، دست نخورد.
|
||||
|
||||
## ۲۴. پر کردنِ سقفِ سراسریِ پیامک
|
||||
|
||||
- **چیست:** `OTP_SMS_GLOBAL_DAILY_MAX` (۳۰۰ در ۲۴ ساعت) جلوی خرجِ بیسقفِ پیامک را میگیرد. هر
|
||||
@@ -304,8 +273,7 @@
|
||||
- **چیست:** `lessons/{id}/questions` و `lessons/{id}/intro` (بیورود)، `progress/nodes`،
|
||||
`user/hearts`، `notifications/{id}/read`، `support/tickets/{id}` و چند مسیرِ دیگر `throttle_scope`
|
||||
ندارند؛ کلِ بانکِ سوال بیورود قابلِ کپی است. (`/api/schema/` سقفِ `read` دارد.)
|
||||
- **چرا باز مانده:** سقفِ مهمانها بر اساسِ IP است (مورد ۲۳)؛ افزودنِ سقف به این مسیرها برای کلاسی
|
||||
که پشتِ یک وایفای با مهمان کار میکند ۴۲۹ میسازد. همراهِ تصمیمِ مورد ۲۳ بسته شود.
|
||||
- **رفع:** برای هر کدام `throttle_scope` و ردیفِ نرخ در `config/settings.py` اضافه شود.
|
||||
|
||||
## ۳۱. لپتاپِ توسعه روی شبکهی محلی
|
||||
|
||||
@@ -324,7 +292,7 @@
|
||||
|
||||
| حفاظ | تست |
|
||||
|---|---|
|
||||
| ورودِ مهمان وجود ندارد (`POST /api/auth/session` ۴۰۴ میدهد و کاربر نمیسازد)؛ حسابِ مهمانِ قدیمی همچنان refresh و ارتقا دارد | `accounts/tests.py` (`test_guest_login_endpoint_is_gone`)، `accounts/tests_guest_refresh.py` |
|
||||
| ورودِ مهمان وجود ندارد؛ مدلِ `User` ستونِ `is_guest`/`device_id` ندارد و migrationِ حذفشان تا مهمانی مانده باشد میشکند | `accounts/migrations/0008_remove_guest_login.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`) |
|
||||
@@ -337,8 +305,7 @@
|
||||
| کتابخانهها بدونِ آسیبپذیریِ شناختهشده (`urllib3>=2.8.0`، `python-dotenv 1.2.2`) | پایگاهِ OSV روی `pip freeze` |
|
||||
| actionهای لغو/بازپرداختِ سفارش، علامتِ «بررسیشده» و لینکِ بازار اجازهی تغییر میخواهند؛ کارمندِ فقطخواندنی (Supervisor) اجرایشان نمیکند | `payment/tests_security_review.py`، `content/tests_security_review.py` |
|
||||
| ورودِ فایلِ سوال/برنامهی درسی اجازهی افزودن (و برای جایگزینی، حذف) میخواهد؛ مرورگرِ محتوا اجازهی دیدنِ سوال | `content/tests_security_review.py` |
|
||||
| شناسهی دستگاهِ مهمان فقط برای ابرکاربر در پنل دیده میشود؛ ثبتِ شماره نشستهای قبلیِ مهمان را باطل میکند | `accounts/tests_security_review.py` |
|
||||
| حذفِ حساب سابقهی کدِ تخفیف، پیشنهادها و کدهای پیامکیِ شماره را نگه میدارد؛ مهمانِ نمایشی حذف نمیشود | `accounts/tests_security_review.py` |
|
||||
| حذفِ حساب سابقهی کدِ تخفیف، پیشنهادها و کدهای پیامکیِ شماره را نگه میدارد؛ حسابِ دمو حذف نمیشود | `accounts/tests_security_review.py` |
|
||||
| صافیِ اطلاعاتِ تماس در نامِ نمایشی (شماره با هر جداکننده، آیدیِ شبکهی اجتماعی) | `accounts/tests_security_review.py` |
|
||||
| کلیدِ کاوهنگار و توکنِ خریدِ بازار در لاگِ خطای شبکه نمیآیند | `accounts/tests_security_review.py` |
|
||||
| برگشت از زرینپال بی`Authority` ۴۰۰؛ خریدِ دومِ بازار روی سفارشِ پرداختشده ۴۰۹؛ آنچه داده یا پس گرفته میشود از خودِ سفارش است، نه پلنِ فعلی | `payment/tests_security_review.py` |
|
||||
@@ -352,9 +319,8 @@
|
||||
| کدِ تخفیفِ وسطِ رفتن به درگاه، مبلغِ authority را با سفارش ناهمخوان نمیکند | `payment/tests_security_review.py` (`DiscountDuringPaymentRequestTests`) |
|
||||
| پرداختِ بستهای که پلنش پاک شده از عددهای ثبتشدهی سفارش اعتبار میدهد (و اگر نداشت ERROR لاگ میشود)؛ پلن فقط با IRT ذخیره میشود | `subscription/tests_security_review.py` (`PaidPackWithDeletedPlanTests`، `PlanCurrencyIsTomanOnlyTests`) |
|
||||
| توکنِ بازار (`refresh_token`، `pending_state`) با فیلترِ querystringِ پنل حرفبهحرف درنمیآید | `payment/tests_security_review.py` (`BazaarTokenNotGuessableByAdminFilterTests`) |
|
||||
| `device_id` با فیلترِ querystringِ پنل برای غیرِ ابرکاربر درنمیآید؛ شماره و `is_guest` را فقط ابرکاربر عوض میکند؛ حسابهای کارمند و گروهها فقط با ابرکاربر ساخته/تغییر میکنند | `accounts/tests_security_review.py` (`DeviceIdNotGuessableByAdminFilterTests`، `StaffCannotSwapLoginPhoneTests`، `StaffAccountsAdminSuperuserOnlyTests`) |
|
||||
| شمارهی کاربر را فقط ابرکاربر عوض میکند؛ حسابهای کارمند و گروهها فقط با ابرکاربر ساخته/تغییر میکنند | `accounts/tests_security_review.py` (`StaffCannotSwapLoginPhoneTests`، `StaffAccountsAdminSuperuserOnlyTests`) |
|
||||
| لاگِ تحویلِ کمپین اجازهی view میخواهد (هر کارمندی نمیبیند) | `campaigns/tests_security_review.py` |
|
||||
| هر رقمِ یونیکد (تمامعرض، ریاضی…) در شماره به لاتین برمیگردد؛ کدِ غیرِ ASCII، `deviceId` null، پایهی ناموجود یا عظیم و تاریخِ تولدِ با «²» ۴۰۰ است نه ۵۰۰؛ صافیِ نامِ نمایشی ارقامِ یونیکد و جداکنندهی دلخواه را میگیرد | `accounts/tests_security_review.py` (`UnicodeInputsAreNormalisedOrRejectedTests`) |
|
||||
| مهمانِ نمایشیِ مشترک با ورودِ یک شماره به آن شماره تبدیل نمیشود | `accounts/tests_security_review.py` (`DemoGuestNotUpgradableTests`) |
|
||||
| هر رقمِ یونیکد (تمامعرض، ریاضی…) در شماره به لاتین برمیگردد؛ کدِ غیرِ ASCII، پایهی ناموجود یا عظیم و تاریخِ تولدِ با «²» ۴۰۰ است نه ۵۰۰؛ صافیِ نامِ نمایشی ارقامِ یونیکد و جداکنندهی دلخواه را میگیرد | `accounts/tests_security_review.py` (`UnicodeInputsAreNormalisedOrRejectedTests`) |
|
||||
| `/api/home?gradeId=` ناموجود کش نمیشود (کشِ پُر شمارندهی سقفِ ورودِ ادمین را پاک میکرد)؛ `/api/progress/nodes` یک کوئری است و `unit` عظیم ۴۰۰ | `content/tests_security_review.py` (`JunkGradeIdsDoNotFlushCacheTests`، `ProgressNodesInputAndQueriesTests`) |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user