9.3 KiB
رفع باگ دسترسی کاملِ پزشکِ مهمان در محیط کلینیک
پروژه
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 ولی بدون اثر:
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 (همهی آیتمهای مدیریتی نمایش داده میشوند):
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):
primaryRole: res.data.context?.role ?? null,
context: res.data.context,
(نوع ContextItem احتمالاً scope? ندارد — باید اضافه شود.)
وظایف
۱. در دسترسبودن scope در authStore
- در
assets/admin/stores/authStore.ts، typeContextItemفیلد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یا یک wrapperClinicScopeBlock) که اگر کاربر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,ClinicServicecontrollers, و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 خودش بدون تغییر.
- پزشکِ مهمان →