feat: implement server-side validation for mobile numbers and national codes across multiple endpoints
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
# گزارش باگ — ممیزی ۱۴۰۵/۰۳/۳۰ (2026-06-20)
|
||||
|
||||
ممیزی رفتاریِ کل APIها (۲۴۳ endpoint) با توکن واقعیِ هر ۶ نقش (admin/clinic/doctor/secretary/representation/user) روی `https://clinic-pro.ddev.site`. مقایسه با `docs/api/*`.
|
||||
|
||||
## خلاصه
|
||||
|
||||
| سطح | تعداد |
|
||||
|-----|------|
|
||||
| CRITICAL | 0 |
|
||||
| HIGH | 1 |
|
||||
| MEDIUM | 2 |
|
||||
| LOW | 0 |
|
||||
| **رفعشده در همین جلسه** | 1 (BUG-1) |
|
||||
|
||||
**نتیجهی کلیِ امنیتی مثبت:** لایهی authorization محکم است — همهی `/api/v1/admin/*` برای نقشهای غیرادمین `403` دادند؛ scope نماینده درست محدود است؛ IDOR روی پروفایل کاربر و ویرایش کلینیکِ غیرمالک `403/404` میگیرد؛ ورودیهای مرزیِ لیستها (page/limit بزرگ/منفی/غیرعددی، تاریخ نامعتبر) همگی graceful بودند (بدون ۵۰۰).
|
||||
|
||||
---
|
||||
|
||||
## باگها
|
||||
|
||||
### [HIGH] BUG-3 — ساخت نماینده با شماره موبایلِ نامعتبر (بدون اعتبارسنجی فرمت)
|
||||
- **دامنه:** representation
|
||||
- **endpoint:** `POST /api/v1/representation` (`ROLE_ADMIN`)
|
||||
- **بازتولید:**
|
||||
```bash
|
||||
curl -sk -X POST .../api/v1/representation -H "Authorization: Bearer <ADMIN>" \
|
||||
-H "Content-Type: application/json" -d '{"full_name":"qa","mobile_number":"not-a-mobile"}'
|
||||
```
|
||||
- **انتظار:** `422` (شماره موبایل نامعتبر).
|
||||
- **واقعیت:** `201 Created` — یک نماینده ساخته شد و یک `User("not-a-mobile")` ایجاد شد (در پاسخ `mobile_number: null`).
|
||||
- **اثر:** رکورد آشغال + کاربرِ بدون شمارهی لاگین معتبر؛ شماره موبایل کلیدِ ورود است، پس نمایندهای ساخته میشود که نمیتواند وارد شود/پیامک بگیرد.
|
||||
- **ریشه:** `src/Representation/Controller/RepresentationController.php:86,89` — فقط `empty($mobile)` چک میشود، فرمت ایران (`^09\d{9}$`) چک نمیشود.
|
||||
- **رفع پیشنهادی:** بعد از `empty` چک، `if (!preg_match('/^09\d{9}$/', $mobile)) return $this->error(ERR_VALIDATION_001, 'شماره موبایل نامعتبر', 422);` (همان regex فرانت).
|
||||
- **وضعیت:** گزارششده (رفع نشده — نیاز به تأیید).
|
||||
|
||||
### [MEDIUM] BUG-2 — کد ملی نامعتبر بدون اعتبارسنجی سمت سرور ذخیره میشود
|
||||
- **دامنه:** user-profile
|
||||
- **endpoint:** `PATCH /api/v1/user-profile/{uuid}`
|
||||
- **بازتولید:**
|
||||
```bash
|
||||
curl -sk -X PATCH .../api/v1/user-profile/<own-uuid> -H "Authorization: Bearer <USER>" \
|
||||
-H "Content-Type: application/json" -d '{"national_code":"1111111111"}'
|
||||
```
|
||||
- **انتظار:** `422` (رقم کنترلیِ کد ملی نامعتبر است).
|
||||
- **واقعیت:** `200` و `national_code: "1111111111"` ذخیره شد.
|
||||
- **اثر:** اعتبارسنجی کد ملی فقط در فرانت (`isValidIranNationalCode`) است؛ هر کلاینت/فرانتِ دورزدهشده میتواند کد ملی نامعتبر ثبت کند.
|
||||
- **ریشه:** `src/UserProfile/Controller/UserProfileController.php` → `hydrate()` فقط `setNationalCode($data['national_code'])` میکند، بدون validation.
|
||||
- **رفع پیشنهادی:** اعتبارسنجی الگوریتم رقم کنترلیِ ایران سمت سرور قبل از `setNationalCode` (پورت همان منطق فرانت).
|
||||
- **وضعیت:** گزارششده.
|
||||
|
||||
### [MEDIUM] BUG-1 — ۵۰۰ روی ساخت بلاگ با تایپ اشتباهِ body ✅ رفع شد
|
||||
- **دامنه:** blog
|
||||
- **endpoint:** `POST /api/v1/blog` (`ROLE_ADMIN`)
|
||||
- **بازتولید:** `-d '{"title":"valid","body":["x"]}'`
|
||||
- **انتظار:** `422`.
|
||||
- **واقعیت (قبل رفع):** `HTTP 500` / `ERR_INTERNAL_001`.
|
||||
- **ریشه:** `src/Blog/Controller/BlogController.php:178` — `trim($data['body'] ?? '')` وقتی `body` آرایه است → `TypeError` در PHP 8.
|
||||
- **رفع انجامشده:** چک `is_string` روی title/body (۴۲۲ صریح) + cast `(string)`. تست شد: body آرایه → `422`، بلاگ معتبر همچنان `201`.
|
||||
- **یادداشت:** اسکن سراسری نشان داد بقیهی controllerها از `(string)` cast استفاده میکنند؛ این تنها نقطهی بدون cast بود.
|
||||
|
||||
---
|
||||
|
||||
## مواردی که تست شدند و **سالم** بودند (شواهد مثبت)
|
||||
|
||||
| تست | نتیجه |
|
||||
|-----|------|
|
||||
| `/api/v1/admin/*` (users, payments, settlements, doctors, clinics, sms/logs) با ۵ نقش غیرادمین | همه `403` ✓ (admin `200`) |
|
||||
| `representation/{me,doctors,clinics,appointments}` | rep `200`، بقیه `403` ✓ |
|
||||
| IDOR: user → PATCH پروفایلِ کاربر دیگر | `404` (canAccess ownership) ✓ |
|
||||
| IDOR: doctor → PATCH کلینیکِ غیرمالک | `403` (`ERR_AUTH_006`) ✓ |
|
||||
| لیستهای paginated: `page=99999`, `limit=-1/0`, `page=abc` | همه `200` graceful ✓ |
|
||||
| detail بلاگها (کلاس باگ proxy author) | همه `200` ✓ (قبلاً رفع شده بود) |
|
||||
| POST با بدنهی خالی `{}` (doctor/clinic/representation/blog/send-code) | همه `422` ✓ |
|
||||
| `tsc --noEmit` + `yarn dev` (admin build) | بدون خطا ✓ |
|
||||
|
||||
---
|
||||
|
||||
## توصیهها
|
||||
|
||||
1. **یک Trait/سرویس اعتبارسنجی مشترک سمت سرور** برای موبایل و کد ملی بساز (الان منطق فقط در فرانت تکرار شده). BUG-2 و BUG-3 هر دو از همین خلأ میآیند.
|
||||
2. الگوی `trim((string) ($data[...] ?? ''))` را بهعنوان قاعده در همهی هندلرها رعایت کن (BUG-1).
|
||||
3. این ممیزی روی محورهای پرریسک متمرکز بود؛ برای پوشش کامل ۲۴۳ endpoint، یک suite `phpunit` رفتاری (functional) ارزش سرمایهگذاری دارد (الان فقط `tests/bootstrap.php` هست).
|
||||
Reference in New Issue
Block a user