Close /api/health details and API docs to the public

Health: public gets only ok/error; details need X-Internal-Key. Docs/schema: admin session only.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 13:05:19 +03:30
parent d6529956e1
commit 22c3196343
9 changed files with 74 additions and 53 deletions

View File

@@ -23,8 +23,6 @@
|---|---|---|---|
| ۲ | ۷ حسابِ دمو با کدِ ورودِ ثابت روی پروداکشن | بالا | `.env` سرور |
| ۳ | داده‌ی دمو روی دیتابیسِ پروداکشن | بالا | دیتابیسِ سرور |
| ۴ | `/api/health` جزئیاتِ داخلی را به همه نشان می‌دهد | متوسط | کد |
| ۵ | Swagger، ReDoc و schemaی کاملِ API عمومی‌اند | متوسط | کد |
| ۷ | پنلِ `/admin/` روی دامنه‌ی عمومی | متوسط | nginx + کد |
| ۸ | سرور بدونِ ArvanCloud روی پورتِ ۸۰ هم جواب می‌دهد | متوسط | ادمینِ سرور |
| ۹ | کشِ مشترک بینِ workerها نیست (Redisِ اختصاصی)؛ سقف‌های مبتنی بر کش ≈ ۳ برابرند | متوسط | تصمیم + ساختِ Redis |
@@ -63,36 +61,6 @@
`purge_demo_data` این‌ها را **نمی‌گیرد** (از مسیرِ واقعیِ viewها ساخته شده‌اند).
- **چطور بسنجید:** `/api/health` → `traffic.users` فقط کاربرانِ واقعی را بشمارد.
## ۴. `/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`) را فهرست می‌کند.
- **خطر:** شناساییِ کاملِ سطحِ حمله بدونِ زحمت.
- **چرا باز مانده:** قراردادِ مشترک با فرانت همین‌جاست؛ فرانت‌کار و ارائه به آن نیاز دارند.
- **رفع:** مسیر را در nginx به IPهای تیم ببندید، یا در `SPECTACULAR_SETTINGS` هر دوِ
`SERVE_PERMISSIONS = ['rest_framework.permissions.IsAdminUser']` و
`SERVE_AUTHENTICATION = ['rest_framework.authentication.SessionAuthentication']` را بگذارید.
`IsAdminUser` به‌تنهایی کار نمی‌کند: احرازِ پیش‌فرضِ DRF فقط توکنِ Bearer است، پس ادمینِ واردشده
به پنل هم ۴۰۱ می‌گیرد (آزموده شد). اسکریپتِ Swagger/ReDoc نسخه‌ی ثابت دارد (نه `@latest`)، چون
روی همان دامنه‌ی `/admin/` اجرا می‌شود.
- **چطور بسنجید:** `curl -o /dev/null -w "%{http_code}" …/api/schema/` باید ۴۰۱/۴۰۳ بدهد.
## ۷. پنلِ ادمین روی دامنه‌ی عمومی
- **چیست:** `/admin/` روی `mathapi.tadrisino2.ir`، با ورودِ نام‌کاربری/رمزِ جنگو. سقفِ ۱۰ تلاش در ۱۵ دقیقه برای هر IP دارد (`AdminLoginThrottleMiddleware`)،
@@ -259,6 +227,8 @@
| پلنِ هر خرید روی سفارش؛ خرید اشتراکِ فعال را دست نمی‌زند؛ یک کدِ تخفیف پیش از درگاه؛ امتیاز فقط از جوابِ ثبت‌شده | `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` |