- CLAUDE.md: 1,225 → ~430 lines, current state only (the dated status log stays in git history). Server rules moved to the top: what is ours on 249/247 and what is not, non-negotiable rules, announce-before-acting, password handling, and the full "deploy it" procedure with verification. - README: formal entry page with current numbers (10 grades, 18,522 questions, 61 API operations, 973 tests, PostgreSQL/Redis). - Removed docs/frontend/ (Figma-era guide with guest login and its own api.ts; the frontend repo has the real client). Swagger's external-docs link now points at the frontend repo. - architecture/operations/security/content-authoring/loadtest/textbooks: SQLite, guest login, demo accounts, question packs and the port-80 note replaced by what is true now. - Technical handbook text updated throughout; all seven diagrams redrawn (PostgreSQL, phone-only login, subscriptions, current data model); PDF rebuilt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
193 lines
26 KiB
Markdown
193 lines
26 KiB
Markdown
# پیش از پروداکشنِ واقعی: چیزهایی که عمداً باز ماندهاند
|
||
|
||
**آخرین بازبینی با کد و سرورِ زنده: ۲۰۲۶-۱۰-۱۳ (۲۱ مهر ۱۴۰۵): حسابهای دمو، nginx (فقط آروان، CSP)، جعلِ IP، سقفِ پیامک.**
|
||
|
||
سرویس زنده است ولی هنوز به روی کاربرِ عمومی باز نشده. چند چیز عمداً یا بهخاطرِ تصمیمِ بازِ طراحی باز مانده است. هر کدام برای یک سرورِ
|
||
عمومی با کاربرِ واقعی (کودک، پرداختِ واقعی) **خطر است** و باید پیش از آمدنِ اولین کاربرِ
|
||
واقعی بسته شود.
|
||
|
||
**برای agentها و توسعهدهندههای بعدی:** این فهرست ادعاست، نه ضمانت؛ ممکن است از این
|
||
تاریخ به بعد چیزی عوض شده باشد. پیش از بستنِ هر مورد، وضعیتِ فعلیاش را با خودِ سرور یا
|
||
کد بسنجید (ستونِ «چطور بسنجید»). مورد که بسته شد، از فهرست برداشته میشود و یک خط در
|
||
«حفاظهایی که هست» (پایینِ همین فایل) میگیرد، با نامِ تستی که آن را نگه میدارد. تاریخ و
|
||
دلیلِ تغییر در پیامِ کامیت است، نه اینجا.
|
||
|
||
هر آزمونِ «از بیرون» با `curl` بدونِ توکن انجام شده و در تاریخِ بالا تأیید شده است.
|
||
|
||
---
|
||
|
||
## خلاصه، به ترتیبِ خطر
|
||
|
||
| # | مورد | خطر | کارِ کیست |
|
||
|---|---|---|---|
|
||
| ۷ | پنلِ `/admin/` روی دامنهی عمومی | متوسط | nginx + کد |
|
||
| ۱۱ | کدِ OTP بیهش در دیتابیس و در فهرستِ پنل | کم | کد |
|
||
| ۱۲ | هزینهی دستیارِ AI با ساختِ شمارهی تازه (هر شماره ۲ سوالِ رایگان) قابلِ بالا بردن است | کم (مالی) | کد |
|
||
| ۱۵ | دسترسیِ سرور: حسابِ مشترک، رمز، sudoِ کامل | بالا | ادمینِ سرور |
|
||
| ۱۶ | فرانت توکن را در `localStorage` نگه میدارد | کم | فرانت |
|
||
| ۲۴ | سقفِ روزانهی پیامکِ کلِ سامانه (۲٬۰۰۰ روی سرور) را یک ناشناس پر میکند و ورود با پیامک برای همه بسته میشود | بالا (دسترسپذیری، هزینه) | تصمیمِ طراحی |
|
||
| ۲۵ | با دانستنِ شمارهی کسی، ورودِ او تا ۲۴ ساعت بسته میشود | متوسط | تصمیمِ طراحی |
|
||
| ۲۷ | کلیدِ جوابِ هر سوال با یک حسابِ دوم درمیآید؛ امتیازِ هر جوابِ درست دو بار داده میشود | کم | تصمیمِ محصولی |
|
||
| ۲۸ | استفادهی دوباره از توکنِ تمدیدِ دزدیدهشده تشخیص داده نمیشود؛ «خروج از همهی دستگاهها» نیست | کم | کد، بعد از ارائه |
|
||
| ۲۹ | پیشنهادِ ثبتنام با حذفِ حساب و ورودِ دوباره با همان شماره دوباره داده میشود | کم | کد |
|
||
| ۳۱ | لپتاپِ توسعه با `mobile-preview` روی شبکهی محلی باز است و در DEBUG کدِ `0000` را میپذیرد | کم | لپتاپِ توسعه |
|
||
|
||
---
|
||
|
||
## ۷. پنلِ ادمین روی دامنهی عمومی
|
||
|
||
- **چیست:** `/admin/` روی `mathapi.tadrisino2.ir`، با ورودِ نامکاربری/رمزِ جنگو. سقفِ ۱۰ تلاش در ۱۵ دقیقه برای هر IP دارد (`AdminLoginThrottleMiddleware`)،
|
||
IP جعلپذیر نیست (فقط از آروان)، ولی ۲FA و محدودیتِ IP ندارد.
|
||
- **خطر:** ادمین همهی محتوا، پلنها، کدهای تخفیف، سفارشها و کاربرها را کنترل میکند.
|
||
- **رفع:** محدود کردنِ `/admin/` در nginx به IPهای تیم یا VPN؛ تغییرِ مسیرِ پیشفرض؛ ۲FA؛
|
||
رمزهای قوی و حسابِ staffِ جدا برای هر نفر.
|
||
|
||
## ۱۱. کدِ OTP بیهش در دیتابیس
|
||
|
||
- **چیست:** `OTPCode.code` متنِ ساده است و در فهرستِ پنلِ ادمین (`OTPCodeAdmin`) دیده میشود.
|
||
توکنِ تمدید (`Session.refresh_token`) هششده است و اینجا شامل نیست.
|
||
- **چرا باز مانده:** تیم برای آزمایش بیپیامک یک ردیفِ `OTPCode` در پنل میسازد و کدش را میبیند.
|
||
- **خطر:** کسی که پنل یا دیتابیس را ببیند، در چند دقیقهی عمرِ هر کد میتواند با آن وارد شود.
|
||
- **رفع:** ذخیرهی هش و حذفِ ستون از فهرستِ پنل، همراه با راهِ دیگری برای ورودِ آزمایشی.
|
||
|
||
## ۱۲. هزینهی دستیارِ AI
|
||
|
||
- **چیست:** هر کاربرِ بیاشتراک روزی ۲ سؤالِ رایگان دارد (`AI_DAILY_QUOTA_FREE`) و مشترک ۴؛ ۲۰ در ساعت. هر شمارهی تازه ۲
|
||
سؤالِ رایگان میگیرد و ساختنِ شماره با سقفِ پیامک (بند ۲۴) محدود است. سقفِ روزانهی کلِ سامانه (`AI_GLOBAL_DAILY_MAX`،
|
||
۳۰۰۰) بیشترین خرجِ روزانه را محدود میکند. طولِ سؤال حداکثر ۲۰۰۰ نویسه است.
|
||
- **خطر:** با چند IP و چند شماره، تا سقفِ سراسری خرج میشود (~۲ میلیون تومان در روز) و سقفِ سراسری برای کاربرانِ
|
||
واقعی هم پر میشود.
|
||
- **رفع:** هشدارِ فعال (نه فقط در `/api/health`) وقتی `aiProvider.state` برابرِ `quota_exhausted` شد یا سقفِ سراسری از
|
||
نیمه گذشت؛ سهمیهی کمتر برای حسابِ تازهساخته.
|
||
|
||
## ۱۵. دسترسیِ سرور
|
||
|
||
- **چیست:** ورود با رمز به حسابِ مشترکِ `administrator` با `sudo (ALL : ALL) ALL` و عضوِ
|
||
`docker` و `lxd`، روی ماشینی که Gitea و ایمیلِ شرکت هم رویش است. رمز یک بار در گفتگو با
|
||
agent گذاشته شده و روی دسکتاپِ صاحبِ پروژه در یک فایلِ متنی نگه داشته میشود.
|
||
- **خطر:** هر نشتِ رمز یعنی دسترسیِ root به کلِ ماشینِ شرکت، نه فقط این پروژه.
|
||
۲۰۲۶-۱۰-۰۴: `PasswordAuthentication yes` در `sshd_config` و پورتِ ۲۲ از اینترنت باز است؛ پس رمز
|
||
از هر جای اینترنت حدسزدنی است (fail2ban سنجیده نشد).
|
||
- **رفع (ادمینِ سرور):** کلیدِ SSH بهجای رمز، تعویضِ رمز، حسابِ جدا برای این پروژه با
|
||
دسترسیِ فقط به `~/mathapp-v2` و یک خطِ `NOPASSWD` فقط برای
|
||
`systemctl restart zareian-mathapp`؛ پاک کردنِ فایلِ رمز از دسکتاپ (Keychain جایگزین است).
|
||
|
||
## ۱۶. نگهداریِ توکن در فرانت
|
||
|
||
- **چیست:** کلاینتِ فرانت (`src/api/index.ts` در مخزنِ فرانت) توکن و refresh را در
|
||
`localStorage` نگه میدارد.
|
||
- **خطر:** هر XSS در سایت هر دو را میخواند.
|
||
- **رفع:** CSPِ سختگیرانه روی `math.tadrisino2.ir` حالا هست (جدولِ حفاظها) و اثرِ XSS را کم میکند؛ مانده: refresh در کوکیِ `HttpOnly`.
|
||
|
||
## ۲۴. پر کردنِ سقفِ سراسریِ پیامک
|
||
|
||
- **چیست:** `OTP_SMS_GLOBAL_DAILY_MAX` (روی سرور ۲٬۰۰۰ در ۲۴ ساعت؛ پیشفرضِ کد ۳۰۰) جلوی خرجِ بیسقفِ پیامک را میگیرد. هر
|
||
ناشناسی با درخواستِ کد برای شمارههای تصادفی آن را پر میکند؛ از یک IP بیجعل ساعتی ۱۰۰ درخواست؛
|
||
با چند IP سقف پر میشود.
|
||
- **بازتولید شد:** ۳۰۰ درخواست پذیرفته و ۳۰۰ پیامک فرستاده شد؛ بعد درخواستِ کاربرِ واقعی ۵۰۳.
|
||
- **خطر:** هیچ ثبتنامِ تازه، ورودِ دوباره یا حذفِ حسابی ممکن نیست؛ هزینهی ۳۰۰ پیامک در روز.
|
||
- **رفع (تصمیمِ طراحی):** بعد از گذشتن از یک حد، CAPTCHA یا مانعِ مشابه پیش از ارسال؛ شمارشِ سقف
|
||
بر اساسِ IP/شبکه؛ هشدار در نیمهی سقف.
|
||
|
||
## ۲۵. بستنِ ورودِ یک شمارهی مشخص
|
||
|
||
- **چیست:** ۱۰ درخواستِ کد در ~۲ ساعت سقفِ روزانهی آن شماره را پر میکند (صاحبش تا ۲۴ ساعت کد
|
||
نمیگیرد)، و ۵ حدسِ غلطِ هر کسی کدِ تازهی او را باطل میکند. هر دو بازتولید شد.
|
||
- **رفع (تصمیمِ طراحی):** کد به درخواستکننده گره بخورد (درخواستِ کد یک شناسهی موقت برگرداند که
|
||
ورود لازمش دارد) و حدسِ غلط برای هر درخواستکننده جدا شمرده شود.
|
||
|
||
## ۲۷. کلیدِ جواب با حسابِ دوم، امتیازِ دوباره
|
||
|
||
- **چیست:** جوابِ غلط `correctOptionId` را برمیگرداند و برای همه یکی است؛ پایانِ درس و پایانِ
|
||
جزیره هر کدام جدا امتیاز میدهند. یک حسابِ دومِ دورریختنی کلید را درمیآورد و حسابِ اصلی همه را درست
|
||
میزند (۴ سوال، ۸۰ امتیاز). رتبهبندی و آمارِ درصدِ غلط آلوده میشوند.
|
||
- **رفع (تصمیمِ محصولی):** امتیازِ هر سوال یک بار؛ گزینهی درست فقط بعد از پایانِ آزمون.
|
||
|
||
## ۲۸. توکنِ تمدیدِ دزدیدهشده
|
||
|
||
- **چیست:** استفادهی دوباره از یک توکنِ تمدیدِ باطلشده فقط ۴۰۱ میگیرد؛ اگر دزد اول تمدید کند،
|
||
صاحبِ حساب بیرون میافتد و دزد میماند. «خروج از همهی دستگاهها» هم نیست.
|
||
- **چرا باز مانده:** در دورانِ ارائه حسابهای دمو را چند نفر همزمان به کار میبردند و باطل کردنِ زنجیره بیرونشان
|
||
میانداخت. آن حسابها پاک شدهاند؛ مانعی نمانده، فقط نوشته نشده است.
|
||
- **رفع:** شناسهی خانواده روی `Session`؛ با استفادهی دوباره کلِ خانواده باطل شود؛ گزینهی خروج از همهجا.
|
||
|
||
## ۲۹. پیشنهادِ ثبتنام بعد از حذفِ حساب
|
||
|
||
- **چیست:** پیشنهادِ خودکارِ ثبتنام به `user_id` گره خورده است؛ حذفِ حساب و ورودِ دوباره با همان
|
||
شماره یک کاربرِ تازه میسازد و پیشنهاد دوباره داده میشود. فقط وقتی اثر دارد که یک Offerِ
|
||
ثبتنام فعال باشد.
|
||
- **رفع:** گره زدنِ پیشنهادِ ثبتنام به هشِ شماره.
|
||
|
||
## ۳۰. مسیرهای بیthrottle
|
||
|
||
- **وضع:** با پیمایشِ resolverِ جنگو هر مسیرِ `/api/` که `throttle_scope` نداشت فهرست شد. پانزده مسیر
|
||
سقف گرفتند (جدولِ حفاظها: `content/tests_read_throttles.py`). دو مسیر عمداً ماندند چون با
|
||
`X-Internal-Key` محافظت میشوند و عمومی نیستند: `rag/chunks` و `payment/orders` (POST).
|
||
`/api/docs/` و `/api/redoc/` هم سقفِ جدا ندارند (صفحهی ایستا؛ بند ۵).
|
||
- **نسنجیده:** رفتارِ سقفِ `read` (۱۲۰ در دقیقه) روی ترافیکِ واقعی؛ پشتِ IPِ مشترکِ مدرسه یا
|
||
CGNAT ممکن است کاربرِ عادی هم به آن برسد (به بندِ «نرخهای throttle» در چکلیستِ لانچ نگاه کنید).
|
||
|
||
## ۳۱. لپتاپِ توسعه روی شبکهی محلی
|
||
|
||
- **چیست:** `mobile-preview/serve.py` عمداً روی کلِ شبکه گوش میدهد (برای گوشیِ روی همان وایفای) و
|
||
`/api/` را به runserverِ محلی میفرستد؛ در DEBUG کدِ `0000` هر شمارهای را وارد میکند و کلیدِ
|
||
GapGPTِ لپتاپ همان حسابِ سرور است. (`docker compose` فقط روی `127.0.0.1` باز است و کلیدِ سرویسِ
|
||
داخلی دیگر پیشفرض ندارد.)
|
||
- **رفع:** روی شبکهی عمومی (کافه، مدرسه) اجرا نشود؛ برای توسعه کلیدهای جدای GapGPT و Qdrant.
|
||
|
||
---
|
||
|
||
## حفاظهایی که هست (تا دوباره باز نشوند)
|
||
|
||
هر تغییری در این مسیرها باید تستهای نامبرده را سبز نگه دارد. جزئیاتِ پرداخت و تخفیف در
|
||
`docs/architecture.md`.
|
||
|
||
| حفاظ | تست |
|
||
|---|---|
|
||
| ورودِ مهمان وجود ندارد؛ مدلِ `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`) |
|
||
| `/api/health` بیکلید فقط `ok`/`error` میدهد؛ جزئیات با هدرِ `X-Internal-Key` (کلید در `.env` سرور). بیکلیدِ تنظیمشده هیچکس جزئیات نمیبیند | `config/tests.py` (`HealthIsMinimalForThePublicTests`) |
|
||
| Swagger، ReDoc و schema فقط برای ادمینِ واردشده به `/admin/` | `config/tests.py` (`ApiDocsRtlShellTests.test_docs_are_closed_to_anonymous_visitors`) |
|
||
| جدولِ رتبهبندی فقط برای کاربرِ واردشده | `progress/tests.py` (`LeaderboardPeriodValidationTests`) |
|
||
| شمارهی موبایل نرمال میشود؛ cooldown، سقفِ روزانهی هر شماره و سقفِ کلِ پیامک در دیتابیس شمرده میشوند | `accounts/tests_otp_limits.py` |
|
||
| OTPِ ۶ رقمی با ۵ حدس برای هر کد؛ خروج از حساب توکنهای قبلی را باطل میکند | `accounts/tests_audit_fixes.py` |
|
||
| قلبِ صفر جوابِ سوال را میبندد؛ چکش داخلِ تراکنش است (بیجوابماندن و جزیرهی قفل جداگانه در ردیفِ بعدی بسته شدهاند) | `content/tests_hearts_gate.py` |
|
||
| `select_for_update` مؤثر (قفلِ ردیفِ Postgres؛ روی SQLiteِ محلی `transaction_mode: IMMEDIATE`)؛ مقایسهی زمانثابتِ `X-Internal-Key` | `payment/tests_audit_fixes.py` |
|
||
| `deploy.sh` پیش از `migrate` بکاپِ `db-before-deploy-*` میگیرد (`pg_dump -Fc` روی Postgres) و اگر نشد migrate را اجرا نمیکند | مرحلهی «بکاپِ دیتابیس» در خروجیِ هر دیپلو |
|
||
| دیتابیس Postgresِ اختصاصی است و هر تغییرش لحظهای روی سرورِ دیگری (۲۴۷) کپی میشود، با نسخهی کاملِ روزانه و بازیابی تا هر لحظه از ۷ روزِ اخیر؛ قطعیِ ۲۴۷ اپ را نمیخواباند و بیش از ۱ گیگ روی دیسکِ ۲۴۹ جمع نمیشود (`docs/operations.md` «بکاپ و بازیابی») | `config/tests_offsite_backup.py` برای هشدارِ `/api/health`؛ روی سرور: بازیابی روی ۲۴۷ با شمارشهای برابرِ ۲۴۹ و یک ردیفِ آزمایشی که ۲ ثانیه بعد آنجا بود |
|
||
| `.env`، کلیدِ امضا، لاگها و بکاپها فقط برای صاحبِ سرویس خواندنیاند (مرحلهی «مجوزِ فایلهای داده» در `deploy.sh`) | روی سرور `sudo -u nobody ls ~/mathapp-v2/.deploy-backups` باید خطای دسترسی بدهد |
|
||
| پانزده مسیرِ خواندنی و نوشتنیِ کمخطر سقفِ نرخ دارند (بانکِ سوال و intro بیورود، `progress/nodes`، `user/hearts`، اعلان، تیکت، پلنها، وضعیتِ اشتراک، سفارش، پیشنهادها، affiliate، تاریخچه و فیدبکِ دستیار، خروج). با نرخِ ۳ در دقیقه درخواستِ چهارم ۴۲۹ میگیرد | `content/tests_read_throttles.py` |
|
||
| ساختنِ دستیِ `OTPCode` و `Session` در پنل فقط برای ابرکاربر (کارمندِ عادی ۴۰۳) | `accounts/tests_security_review.py` (`StaffCannotMintLoginsTests`) |
|
||
| پایانِ درس و پایانِ آزمونِ جزیره جوابِ ثبتشده به همهی سوالها میخواهند (۴۰۹ `answers_missing`)، روی جزیرهی قفل ۴۰۳ `node_locked` میدهند (همان قاعدهی نقشه)، جزیره یا درسِ بیسوال چیزی برای جوابدادن ندارد و اگر باز است بیامتیاز رد میشود (قفل بودنش هنوز چک میشود)، و تعدادِ سوالِ آزمون را کلاینت نمیتواند کمتر از آزمونِ استانداردِ ۴ تایی بگوید (ستارهی دروغین) | `content/tests_finish_requires_answers.py`، `content/tests_node_completion.py` |
|
||
| کلیدِ امضای توکن تصادفی و فقط روی سرور است (`.django_secret_key`، ۶۰۰)؛ نه در `.env`، نه در یونیت؛ توکنِ ساختهشده با کلیدِ داخلِ ریپو `401` میگیرد؛ `DJANGO_DEBUG` هم در یونیت نیست | دستی (۲۰۲۶-۱۰-۰۷): توکنِ جعلی `200` ← `401`؛ تستِ واحد ندارد |
|
||
| با `CACHE_URL` ستشده، قطعیِ Redis API را ۵۰۰ نمیکند (fail-open، ERROR در لاگ، health ۵۰۳؛ شرح در `config/cache.py`). روی سرور به Redisِ اختصاصیِ خودمان (`zareian-redis`) ست است، پس سقفهای مبتنی بر کش بینِ workerها مشترکاند؛ بکاندِ `ResilientRedisCache` روی سرور دیده شد، رگبارِ موازی برای سنجیدنِ سقفِ واحد زده نشد | `config/tests_cache_resilience.py` (۱۱ تست، با redis-serverِ محلی که وسطِ کار میمیرد) |
|
||
| بدنهٔ درخواست بزرگتر از ≈ ۲٫۵ مگابایت `413` میگیرد و ERRORِ لاگ نمیسازد (پیشتر `500` + traceback در لاگ؛ `RequestDataTooBig`). nginx هنوز تا ۱۰۰ مگابایت بافر میکند (بند ۱۸) | `config/tests_request_size.py` |
|
||
| کتابخانهها بدونِ آسیبپذیریِ شناختهشده (`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` |
|
||
| برگشت از زرینپال بی`Authority` ۴۰۰؛ خریدِ دومِ بازار روی سفارشِ پرداختشده ۴۰۹؛ آنچه داده یا پس گرفته میشود از خودِ سفارش است، نه پلنِ فعلی | `payment/tests_security_review.py` |
|
||
| شکلِ خرابِ مدل جواب را رایگان نمیکند؛ علامتِ «خارج از ریاضی» در هر جای استریم جواب را رد میکند و در کش نمیرود؛ بستنِ صفحه جوابِ ناقص را ثبت میکند؛ collectionِ بیرون از `KNOWN_COLLECTIONS` جستجو نمیشود | `ai_tutor/tests_security_review.py` |
|
||
| ورودیِ عددیِ غیرِ ASCII (مثلِ `²`) ۴۰۰ است نه ۵۰۰؛ متنِ تیکت/گزارشِ باگ حداکثر ۵۰۰۰ نویسه | `content/tests_security_review.py` |
|
||
| `INTERNAL_SERVICE_KEY` در هیچ حالتی پیشفرض ندارد | `payment/tests.py` (`test_debug_mode_has_no_default_internal_key`) |
|
||
| کلیدِ امضای خودساخته (`.django_secret_key`) یک بار و اتمی ساخته میشود؛ سه workerِ همزمان یک کلید دارند | دستی: سه پروسهی همزمان که `config.settings` را import میکنند، بیفایلِ کلید؛ ۲۰ از ۲۰ یک کلید |
|
||
| فرانت: شمارهی سفارشِ برگشتی از درگاه فقط عدد است، مسیرها encode میشوند و «پرداخت موفق» فقط از پاسخِ سرور میآید؛ دو تبِ همزمان با تمدیدِ توکن خارج نمیشوند | مخزنِ فرانت: `src/app/launchParams.ts`، `src/api/http.ts` |
|
||
| سفارش و تراکنش در پنل کاملاً فقطخواندنیاند (کارمندِ لغو/بازپرداخت مبلغ یا اعتبارِ سفارش را عوض نمیکند) و از پنل ساخته نمیشوند | `payment/tests_security_review.py` (`OrderAdminMoneyFieldsReadOnlyTests`) |
|
||
| ردِ بازار توکنِ خرید را نمیسوزاند؛ سفارشِ بازپرداختشده توکنِ تازه نمیپذیرد | `payment/tests_security_review.py` (`BazaarRejectedVerifyDoesNotBurnTokenTests`) |
|
||
| کدِ تخفیفِ وسطِ رفتن به درگاه، مبلغِ 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`) |
|
||
| شمارهی کاربر را فقط ابرکاربر عوض میکند؛ حسابهای کارمند و گروهها فقط با ابرکاربر ساخته/تغییر میکنند | `accounts/tests_security_review.py` (`StaffCannotSwapLoginPhoneTests`، `StaffAccountsAdminSuperuserOnlyTests`) |
|
||
| لاگِ تحویلِ کمپین اجازهی view میخواهد (هر کارمندی نمیبیند) | `campaigns/tests_security_review.py` |
|
||
| هر رقمِ یونیکد (تمامعرض، ریاضی…) در شماره به لاتین برمیگردد؛ کدِ غیرِ ASCII، پایهی ناموجود یا عظیم و تاریخِ تولدِ با «²» ۴۰۰ است نه ۵۰۰؛ صافیِ نامِ نمایشی ارقامِ یونیکد و جداکنندهی دلخواه را میگیرد | `accounts/tests_security_review.py` (`UnicodeInputsAreNormalisedOrRejectedTests`) |
|
||
| روی سرور هیچ شمارهای با کدِ ورودِ ثابت نیست (`TEST_LOGIN_PHONES` خالی)؛ حسابهای دمو و دادهشان پاک شدند؛ اگر کسی آن را دوباره پر کند `/api/health` هشدار میدهد | `config/tests.py` (هشدارِ health)؛ روی سرور: `/api/health` بیهشدار |
|
||
| فقط ArvanCloud به nginxِ دو سایتِ ما میرسد؛ درخواستِ مستقیم به IPِ سرور ۴۰۳ (`ops/nginx/zareian-arvan-only.conf`) | از بیرون: پورتِ ۸۰ و ۴۴۳ مستقیم ۴۰۳، از راهِ آروان ۲۰۰ |
|
||
| فرانت CSP (فقط منابعِ خودمان، اسکریپتِ درونخطی با هش)، `frame-ancestors 'none'`، `X-Frame-Options`، `nosniff`، HSTS و `Permissions-Policy` دارد؛ سقفِ بدنهی nginxِ API ۲۰ مگابایت | `curl -sI`؛ اپ در Chrome با و بی ورود: صفر خطای CSP |
|
||
| X-Forwarded-For فقط وقتی پذیرفته میشود که peerِ TCP (هدرِ `X-Real-IP`ِ nginx) در `TRUSTED_PROXY_NETWORKS` (رنجهای ArvanCloud) باشد؛ درخواستِ مستقیم به IP با XFFِ جعلی دیگر سقفهای IP-محور را دور نمیزند | `config/tests_trusted_proxy.py`؛ روی سرور: ۶۵ درخواستِ health با XFFِ چرخان، بعد از ۶۰ تا ۴۲۹ |
|
||
| `/api/home?gradeId=` ناموجود کش نمیشود (کشِ پُر شمارندهی سقفِ ورودِ ادمین را پاک میکرد)؛ `/api/progress/nodes` یک کوئری است و `unit` عظیم ۴۰۰ | `content/tests_security_review.py` (`JunkGradeIdsDoNotFlushCacheTests`، `ProgressNodesInputAndQueriesTests`) |
|
||
|