Files
clinicpro/.claude/prompt/fix-guest-doctor-clinic-context-access.md
T

116 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# رفع باگ دسترسی کاملِ پزشکِ مهمان در محیط کلینیک
## پروژه
`clinicpro` (Admin React SPA + گارد Backend). ادامه/تکمیلِ پرامپت قبلی `fix-access-scoping-doctor-clinic-representation.md`.
## زمینه
در پرامپت قبلی، نقشِ context پزشکی که به کلینیک دعوت می‌شود از `clinic` به `doctor` + `scope: clinic` تغییر کرد (در `buildAvailableContexts`). این درست بود ولی **ناقص**: هیچ‌جای فرانت و بک‌اند به فیلد `scope` توجه نمی‌کند. در نتیجه وقتی پزشکِ مهمان به محیط کاریِ آن کلینیک سوییچ می‌کند (`db_uuid = clinicUuid`)، همان منوی کاملِ نقش `doctor` را می‌بیند — شامل **پرسنل، منشی‌ها، سرویس‌ها، اشتراک، پرونده بیماران** — و عملاً به امکانات مدیریتیِ محیط کلینیک دسترسی دارد. این یعنی باگ «پزشکِ اضافه‌شده به کلینیک، دسترسی به همه‌ی امکانات کلینیک دارد» هنوز باقی است.
رفتار درست: پزشکِ مهمان در محیط کلینیک فقط باید **نوبت‌های خودش در آن کلینیک** را ببیند (read/منظوره‌ی محدود)، نه ابزارهای مدیریت کلینیک. در محیط «مطب شخصی» خودش (context با `scope` خالی) همه‌ی ابزارها در دسترس می‌ماند.
## مشکل / هدف
`scope: 'clinic'` که روی context پزشکِ مهمان ست شده، باید واقعاً دسترسی را محدود کند — هم در منوی Sidebar، هم در RoleRouteهای SPA، و هم با گارد در endpointهای مدیریتی (defense in depth).
## فایل‌های مرتبط
| فایل | نقش |
|------|-----|
| `src/Auth/Controller/AuthController.php` | `buildAvailableContexts` (از قبل `scope: 'clinic'` می‌دهد) و `userinfo`/`switchContext` که `context` را برمی‌گرداند |
| `assets/admin/stores/authStore.ts` | نگه‌داری `context` فعال + `primaryRole`؛ باید `scope` در دسترس باشد |
| `assets/admin/components/layout/Sidebar.tsx` | منوی نقش `doctor` — باید در `scope: clinic` محدود شود |
| `assets/admin/App.tsx` | `RoleRoute` — صفحات مدیریتیِ پزشک باید در `scope: clinic` مسدود شوند |
| `src/Staff/Controller/StaffController.php` | `resolveEntity` پزشک = scope شخصی؛ گارد لازم برای حالت مهمان |
| `src/Secretary/Controller/SecretaryController.php` | همان منطق برای منشی‌ها |
| `src/ClinicService/Controller/*` (سرویس‌ها) | همان منطق |
| `docs/api/auth.md` | مستندسازی معنای `scope` |
## وضعیت فعلی (کد واقعی)
`buildAvailableContexts` — context مهمان دارای `scope` ولی بدون اثر:
```php
foreach ($this->clinicRepo->findByDoctor($doctor) as $clinic) {
$isOwner = $clinic->getUser()->getId() === $user->getId();
$contexts[] = [
'type' => 'clinic',
'db_uuid' => $clinic->getUuid(),
'name' => $clinic->getName() ?? '',
'role' => $isOwner ? 'clinic' : 'doctor',
'scope' => $isOwner ? null : 'clinic', // ← ست می‌شود ولی هیچ‌جا مصرف نمی‌شود
'doctor_uuid' => $doctor->getUuid(),
];
}
```
`Sidebar.tsx` — منوی پزشک، بدون توجه به scope (همه‌ی آیتم‌های مدیریتی نمایش داده می‌شوند):
```tsx
if (primaryRole === "doctor") {
return [
{ label: "عمومی", items: [{ to: "/admin/dashboard", ... }] },
{ label: "پروفایل", items: [{ to: "/admin/profile", ... }] },
{ label: "مدیریت", items: [
{ to: "/admin/appointments", label: "نوبت‌های من" },
{ to: "/admin/my-patients", label: "پرونده بیماران" },
{ to: "/admin/staff", label: "پرسنل" }, // ← نباید برای مهمان باشد
{ to: "/admin/my-secretaries", label: "منشی ها" }, // ← نباید
{ to: "/admin/clinic-services",label: "سرویس‌ها" }, // ← نباید
...
]},
];
}
```
`authStore.ts` — context فعال نگه‌داری می‌شود (شامل `role` و باید `scope`):
```ts
primaryRole: res.data.context?.role ?? null,
context: res.data.context,
```
(نوع `ContextItem` احتمالاً `scope?` ندارد — باید اضافه شود.)
## وظایف
### ۱. در دسترس‌بودن `scope` در authStore
- در `assets/admin/stores/authStore.ts`، type `ContextItem` فیلد `scope?: string | null` را داشته باشد و از `userinfo`/`switchContext` پر شود.
- یک selector/مقدار مشتق در store یا کامپوننت‌ها: `isClinicScopedDoctor = primaryRole === 'doctor' && context?.scope === 'clinic'`.
### ۲. محدودکردن منوی Sidebar برای پزشکِ مهمان
در `Sidebar.tsx`، شاخه‌ی `primaryRole === "doctor"`:
- اگر `context?.scope === 'clinic'` بود، فقط آیتم‌های مجاز را برگردان:
- داشبورد (`/admin/dashboard`)
- نوبت‌ها (`/admin/appointments`) — نوبت‌های پزشک در آن کلینیک
- آیتم‌های `staff`, `my-secretaries`, `clinic-services`, `subscription`, `my-patients`, `profile` (مدیریت/داده‌ی شخصیِ مطب) را در این حالت **نشان نده**.
- در محیط «مطب شخصی» (`scope` خالی) منوی کامل فعلی بماند.
### ۳. گارد RoleRoute در SPA برای صفحات مدیریتیِ مهمان
در `App.tsx`:
- یک گارد سبک اضافه کن (مثلاً گسترش `RoleRoute` یا یک wrapper `ClinicScopeBlock`) که اگر کاربر `doctor` با `context.scope === 'clinic'` است، مسیرهای `staff`, `my-secretaries`, `clinic-services`, `subscription`, `my-patients`, `profile` را به `/admin/dashboard` ریدایرکت کند.
- مسیرهای `dashboard` و `appointments` باز بمانند.
### ۴. گارد Backend (defense in depth)
حتی با محدودیت UI، endpointهای مدیریتی نباید به پزشکِ مهمان اجازه‌ی عمل روی کلینیک بدهند. الگوی موجود این است که `StaffController::resolveEntity` برای `ROLE_DOCTOR` همیشه scope **شخصیِ** پزشک را برمی‌گرداند (`['doctor', doctorId]`) — یعنی پزشک فقط پرسنل/سرویسِ مطب خودش را می‌بیند، نه کلینیک. این را **تأیید کن** و مطمئن شو هیچ endpoint مدیریتی، `db_uuid`/clinicUuid را از کلاینت گرفته و بدون چک مالکیت (`clinic.getUser() === currentUser`) روی کلینیک عمل نمی‌کند:
- `StaffController`, `SecretaryController`, `ClinicService` controllers, و `ClinicController` (update/address/logo) را مرور کن؛ هر مسیری که کلینیک را با `findByUuid($clinicUuid)` می‌گیرد باید مالکیت یا `ROLE_ADMIN` را چک کند (الگوی `$clinic->getUser()->getId() !== $user->getId() && !hasRole('ROLE_ADMIN')` → 403). اگر جایی این چک نیست، اضافه کن.
- نتیجه‌ی مورد انتظار: پزشکِ مهمان (نه مالک، نه ادمین) روی این endpointها `403` بگیرد.
### ۵. مستندسازی
- `docs/api/auth.md`: در توضیح `context`/`available_contexts`، معنای `scope: 'clinic'` را شرح بده: «پزشکِ مهمان در محیط کلینیک؛ فقط نوبت‌های خودش، بدون دسترسی مدیریتی به کلینیک».
## نکات مهم
- **منبع حقیقتِ scope = context فعال**، نه `dbUuid` تنها. تمایز «مالک کلینیک» (`role: clinic`)، «پزشک در مطب شخصی» (`role: doctor`, بدون scope)، و «پزشک مهمان در کلینیک» (`role: doctor`, `scope: clinic`).
- در محیط مطب شخصی هیچ محدودیتی اضافه نشود؛ فقط `scope: clinic` محدود می‌شود.
- گارد UI برای UX است؛ **گارد واقعی Backend است** — هر دو لازم‌اند (وظیفه ۴ نباید رها شود).
- ادمین (`ROLE_ADMIN`) همیشه دسترسی کامل دارد؛ مالک کلینیک (`role: clinic`) هم بدون تغییر.
- بعد از تغییر backend، `cache:clear` و به‌روزرسانی `docs/api/auth.md`. تغییر فرانت با `tsc --noEmit` + `yarn dev` تست شود.
- **تست‌ها** (با `lexik:jwt:generate-token`):
- پزشکِ مهمان → `userinfo` آن کلینیک `role:doctor, scope:clinic`؛ بعد از switch، Sidebar فقط «داشبورد» و «نوبت‌ها»؛ مسیرهای `/admin/staff` و `/admin/clinic-services` ریدایرکت به داشبورد.
- همان پزشک روی endpoint مدیریتی کلینیک (مثلاً ساخت پرسنل برای آن کلینیک) → `403`.
- مالک واقعی کلینیک → دسترسی کامل بدون تغییر.
- پزشک در مطب شخصی → منوی کامل و دسترسی به staff/services خودش بدون تغییر.