⬢ تقرير فني — للعرض الإداري

منظومة المستخدمين والأدوار والصلاحيات:
تشريح عطل «تعليق الدخول» وتدقيق المنظومة كاملة

بدأ التدقيق من واقعة محددة: مستخدم جديد بدور «مندوب توصيل» علِق التطبيق معه فور تسجيل الدخول. أعدنا إنتاج العطل بمستخدم تجريبي حي، وحددنا السبب الجذري بالملف والسطر، ثم وسّعنا التدقيق ليشمل دورة حياة الصلاحية كاملة — من الزارع إلى الواجهة.

📅 التاريخ: 10 يونيو 2026 🧪 النطاق: Moon ERP — الواجهة والخادم 🔍 المنهجية: 4 مسارات متوازية + إعادة إنتاج حية + مراجعة Codex
السبب الجذري محدد ومُعاد إنتاجه حياً
1
عطل حرج (حلقة توجيه لا نهائية)
14
مشكلة موثقة (1 حرجة / 7 كبيرة / 6 ثانوية)
34
ملاحظة دليلية خام عبر 4 مسارات
0.3s
الخادم بريء — أبطأ استجابة مقيسة
1

الملخص التنفيذي

Executive Summary

العطل ليس بطئاً ولا مشكلة خادم: هو حلقة توجيه لا نهائية داخل الواجهة. عندما يرفض حارس الصلاحيات صفحةً، يعيد التوجيه إلى /dashboard — لكن /dashboard نفسها تتطلب صلاحية core.dashboard التي لا يملكها دور المندوب، فيرفضها الحارس ويعيد التوجيه إليها من جديد… إلى ما لا نهاية. المستخدم يرى «تحميلاً أبدياً» بينما الموجّه يدور على نفسه.

الخبر الجيد (مُثبت بالقياس): الخادم سليم تماماً — سجّلنا دخولاً حياً بمستخدم تجريبي يحمل نفس الدور: كل النداءات أجابت في 0.21–0.29 ثانية، والدور نفسه (courior) أُنشئ صحيحاً من شاشة الأدوار وتوسّعت تبعياته كما يجب. المشكلة محصورة في منطق التوجيه بالواجهة، وحلّها الجذري يومان عمل.

التدقيق الموسّع كشف أن هذه الواقعة عرَض لنمط أعمق: لا يوجد «حد أدنى» معرّف لدور يستطيع الدخول، ومساران مختلفان لحفظ الأدوار بقواعد مختلفة، وحراسة الشاشات تعتمد على إخفاء عناصر القائمة أكثر من اعتمادها على حراسة فعلية — التفاصيل والحلول في القسمين 3 و4.

2

تشريح العطل خطوة بخطوة

Anatomy of the Hang — reproduced live

حمولة الدخول الفعلية للمستخدم التجريبي (نفس دور حالة ctest — والدور الحقيقي خلفها اسمه courior — بهذا الإملاء في قاعدة البيانات نفسها):

"roles": ["courior"],
"permissions": ["lis.samples.view", "lis.dashboard.view", "lis.samples.pickup"],
"home_page": null,  "data_scope": "all"
1
تسجيل دخول ناجح. الدور صحيح والصلاحيات الثلاث وصلت كما يجب (تبعية lis.samples.view توسّعت تلقائياً).
auth.effects.ts:15-38
2
لا توجد صفحة هبوط للدور.home_page = null (قالب المندوب الجاهز لا يضبطها) → الواجهة تتجه للمسار الافتراضي /.
auth.effects.ts:40-52
UserResource.php:38
3
المسار / يحوّل تلقائياً إلى /dashboard — لوحة الـ ERP الرئيسية.
app.routes.ts:41-43
4
الرفض./dashboard تتطلب core.dashboard — والمندوب يملك lis.* فقط. (مفارقة: القالب يمنحه lis.dashboard.view — لكنها صلاحية للوحة المعمل لا لوحة الـ ERP، بل وتبيّن أنها صلاحية «وهمية» لا يستخدمها أي مسار خادمي.)
app.routes.ts:46-51
auth.guard.ts:84-91
الحلقة. منطق الفشل في الحارس: «وجّه إلى /dashboard» — أي إلى نفس الصفحة التي رفضته للتو. توجيه → رفض → توجيه… لا توجد أي شبكة أمان (لا معالج أخطاء تنقّل، لا كاشف حلقات، لا صفحة «غير مصرّح»). الملاحة لا تكتمل أبداً والشاشة تعلق.
auth.guard.ts:94
app.config.ts:146
نطاق أوسع من المندوب: نفس الحلقة تصيب أي مستخدم لا يملك core.dashboard — من أي حارس فاشل، ومن حارس الموديولات، وحتى من أي رابط خاطئ (المسار الشامل ** يصبّ في نفس القمع). كل الأدوار المعملية المصغّرة بلا صفحة هبوط مرشّحة للسقوط فيها.
3

المشاكل بالتفصيل والحلول

Problems & Fixes — with line-level evidence
حرجة

1 — حلقة التوجيه اللانهائية بعد رفض الصلاحية (السبب الجذري)جهد صغير

مسار فشل حارسَي الصلاحيات والموديولات يستهدف /dashboard المحروسة بـcore.dashboard — أي مستخدم لا يملكها يدخل دوامة أبدية بدل رسالة واضحة. لا يوجد withNavigationErrorHandler ولا أي قاطع حلقات في إعداد الموجّه.
auth.guard.ts:94,109app.routes.ts:41-51,316-318app.config.ts:146
الحل: مُحلّل هبوط ذكي يحسب أول مسار مسموح من صلاحيات المستخدم (أو صفحة «غير مصرّح» مخصصة عند انعدامها) ليحل محل التوجيه الثابت، + تفعيل معالج أخطاء التنقل. يَكسر الحلقة لكل الأدوار الحالية والمستقبلية.
كبيرة

2 — قالب «المندوب» الجاهز يُنتج دوراً بلا صفحة هبوط — فيسقط مباشرة في الحلقةجهد صغير

القالب لا يضبط صفحة هبوط، وكتالوج صفحات الهبوط في الخادم لا يتضمن أصلاً شاشة المندوب /lab/courier-pickups — فحتى الأدمن الحريص لا يستطيع اختيارها. كما يتضمن الكتالوج مسارات ميتة (/lab/receiving محذوفة) تصبّ بدورها في الحلقة.
lis-roles.component.ts:45,253-259LabRoleController.php:29-40
الحل: إضافة شاشة المندوب للكتالوج وضبطها افتراضياً في القالب، وتنقية المسارات الميتة، وجعل صفحة الهبوط حقلاً شبه إلزامي عند إنشاء أي دور معملي.
كبيرة

3 — مساران مختلفان لحفظ الأدوار بقواعد مختلفةجهد متوسط

شاشة أدوار النظام (Core) تحفظ بدون توسعة التبعيات وبدون صفحة هبوط وبدون نطاق بيانات، وتنهار بخطأ 500 على أسماء صلاحيات مجهولة — بينما شاشة أدوار المعمل تطبّق كل ذلك. نفس الدور يُنتَج مختلفاً حسب الشاشة التي أنشأته.
RoleController.php:79-119LabRoleController.php:121-168
الحل: توحيد منطق الحفظ في خدمة واحدة (توسعة + هبوط + نطاق + تحقق) يستهلكها المساران.
كبيرة

4 — لا يوجد «حد أدنى لدور قابل للدخول» — والدور الفارغ مسموحجهد صغير

خادمياً، كلا مساري الحفظ يقبلان إنشاء دور بصفر صلاحيات (لا قاعدة min:1)، والواجهة تشترط صلاحية واحدة فقط أياً كانت — فيُنتَج مستخدمون «ليسوا سوبر أدمن لكن لا يرون شيئاً»، وكلهم يسقطون في الحلقة. ولا توثيق لأي حزمة دنيا يحتاجها الدخول السليم.
LabRoleController.php:167-168roles.component.ts:379-390
الحل: قاعدة تحقق عند الحفظ: لا دور بدون صلاحية هبوط واحدة على الأقل + صفحة هبوط صالحة؛ وتوثيق «الحزمة الدنيا» في شاشة الأدوار.
كبيرة

5 — عشرات الشاشات المعملية (62 مساراً) بلا أي حراسة مسارجهد متوسط

كل المسارات الفرعية تحت /lab — 62 تعريف مسار، تحققنا منها عدّاً — (وكذلك توأمها المدمج) لا تحمل أي متطلب صلاحية — أي مستخدم يملك صلاحية معملية واحدة يفتح أي شاشة معملية بكتابة الرابط، والحماية الفعلية متروكة كلياً لأخطاء 403 من الخادم.
lis-standalone.routes.ts (all children)app.routes.ts:183-224
الحل: إلحاق data.permissions لكل شاشة (نفس خريطة صلاحيات القائمة الجانبية الموجودة فعلاً — نقل لا تأليف).
كبيرة

6 — الأدوار غير معزولة بين الشركاتجهد متوسط

جدول الأدوار بلا أي نطاق شركة، وكلتا شاشتي الأدوار تعرضان كل أدوار النظام لكل الشركات — تسريب متعدد المستأجرين في نظام يُسوَّق بهذه الخاصية.
Modules/Core .. Role model — لا يوجد company_id (تحقق المسار الرابع)RoleController.php + LabRoleController.php (both indexes)
الحل: ربط الأدوار بالشركة (عمود company_id + نطاق عام) مع خطة هجرة للأدوار المشتركة الحالية.
كبيرة

7 — «سوبر أدمن» الواجهة بلا مقابل في الخادمجهد متوسط

الواجهة تعتبر من بلا أدوار وبلا صلاحيات «سوبر أدمن» وتفتح له كل القوائم — بينما الخادم لا يعرف هذا المفهوم فيرد 403 على كل شيء: واجهة كاملة من الأزرار المكسورة. (والعكس: مستخدم بدور فارغ يرى لا شيء ويعلق.)
auth.guard.ts:86grep شامل للمستودع: صفر Gate::before
الحل: توحيد التعريف: إمّا Gate::before خادمياً لنفس الشرط، أو إلغاء الإشارة الواجهية واستبدالها بدور صريح super-admin.
كبيرة

8 — شاشة المستخدمين تمنح دور «admin» افتراضياً لكل مستخدم جديدجهد صغير

نموذج إنشاء المستخدم يبدأ بالدور admin محدداً مسبقاً — أدمن واحد غافل = مستخدم عادي بصلاحيات إدارية. خيار افتراضي خطير بلا مبرر.
users.component.ts:223,230
الحل: الافتراضي قائمة فارغة مع إلزام اختيار دور واحد على الأقل.
ثانوية

9 — نطاق البيانات يفشل «مفتوحاً»جهد صغير

عند غياب تحديد نطاق البيانات للدور تكون النتيجة all — مندوب جديد يرى بيانات كل الفروع، أي أن مستخدماً قُصد تقييده بفرعه قد يطّلع على بيانات بقية الفروع حتى يُضبط النطاق يدوياً. الافتراض الآمن هو الأضيق لا الأوسع.
UserResource.php:29-36LabRoleController.php:121
الحل: الافتراضي branch مع رفع صريح من الأدمن عند الحاجة.
ثانوية

10 — صلاحيات ملغاة تظل فعّالة في الواجهةجهد صغير

عند فشل جلب الملف الشخصي تسقط الواجهة لنسخة مخزّنة قديمة من المستخدم — صلاحيات سُحبت منه تظل تفتح الشاشات في الواجهة حتى يسجّل خروجاً (البيانات نفسها تظل محمية — الخادم يرفض نداءاته بـ 403)؛ ولا مهلة زمنية على نداء الملف الشخصي أصلاً.
auth.guard.ts:36-41auth.effects.ts:81-108
الحل: مهلة + إبطال النسخة المخزنة عند 401/403 وإجبار إعادة الدخول.
ثانوية

11 — مطابقة البادئة توسّع الرؤية بلا قصدجهد متوسط

lis.samples.pickup «تبدأ بـ» lis.samples — فيرى المندوب قوائم سحب العينات والاستقبال أيضاً. نظام البادئة لا يفرّق بين «أفعال» داخل نفس المورد، ويتفاقم مع غياب حراسة المسارات (مشكلة 5).
auth.guard.ts:89-91lis-layout.component.ts:159-160
الحل: مطابقة على حدود المقاطع (segment-aware) بدل startsWith الخام.
ثانوية

12 — صلاحية «لوحة المعمل» وهميةجهد صغير

lis.dashboard.view مزروعة وممنوحة في كل القوالب، لكن لا يستخدمها أي مسار خادمي — وبيانات لوحة المعمل الفعلية تتطلب صلاحيات أخرى لا يملكها المندوب، فلوحته تفتح فارغة بأخطاء.
RolePermissionSeeder.php:700اختبار حي: GET api/lis/dashboard → 404
الحل: ربط الصلاحية فعلياً بنداءات اللوحة أو إزالتها من الكتالوج، وتعريف ما تحتاجه اللوحة بدقة.
ثانوية

13 — أخطاء تكامل صغيرة متفرقةجهد صغير

زر «العودة للـ ERP» في تخطيط المعمل يفحص البادئة hr. بينما الصلاحيات hrm.* (لا يطابق أبداً)؛ صفحة الهبوط عند تعدد الأدوار تُحسم اعتباطياً بأول قيمة؛ وGET /lis/roles/{id} يعيد صفحة HTML بدل JSON (مسار ناقص).
lis-layout.component.ts:128UserResource.php:38
الحل: تصحيحات نقطية (بادئة hrm، أولوية هبوط معرّفة، إضافة مسار show).
ثانوية

14 — تشغيلية: وضع التصحيح مفعّل على بيئة حية + كاش صلاحيات هشجهد صغير

APP_DEBUG=true فعلياً (سطر تالف في ملف البيئة يلغي القيمة المقصودة false) — كل خطأ 403/404 يسرّب أثر مكدس كاملاً؛ وحفظ الأدوار غير معاملاتي، وكاش صلاحيات spatie ملفّي وقد ينكسر بصمت بملكيّات ملفات خاطئة على هذه الاستضافة.
.env:4,70اختبار حي: استجابة 403 تتضمن stack trace كاملاًRoleController.php:81-88
الحل: إصلاح سطر البيئة، تغليف الحفظ بمعاملة، ونقل كاش الصلاحيات إلى المخزن العام.
4

خطة العلاج — ثلاث مراحل

Remediation Roadmap
المرحلة 1

كسر الحلقة — إصلاح الواقعة

⏱ يومان · يعالج: 1، 2، 8، 14 (البيئة)
  • مُحلّل هبوط ذكي (أول مسار مسموح) + صفحة «غير مصرّح» + معالج أخطاء التنقل
  • صفحة هبوط لقالب المندوب + إضافة شاشته للكتالوج وتنقية المسارات الميتة
  • إلغاء افتراضي admin في شاشة المستخدمين
  • إصلاح سطر APP_DEBUG التالف
المرحلة 2

تصليب دورة حياة الدور

⏱ أسبوع · يعالج: 3، 4، 5، 9، 10، 12
  • خدمة حفظ أدوار موحّدة (توسعة + هبوط + نطاق + تحقق) للمسارين
  • قاعدة «الحد الأدنى لدور قابل للدخول» + منع الدور الفارغ
  • حراسة مسار لكل شاشة معملية + نطاق بيانات يفشل مغلقاً
  • مهلة الملف الشخصي + إبطال النسخة المخزنة
المرحلة 3

المعمارية والاختبارات

⏱ أسبوعان · يعالج: 6، 7، 11، 13 ويمنع التكرار
  • عزل الأدوار بالشركة (multi-tenant) مع خطة هجرة
  • توحيد تعريف السوبر أدمن واجهةً وخادماً
  • مطابقة صلاحيات على حدود المقاطع
  • مصفوفة اختبار دخول آلية: لكل قالب دور جاهز، اختبار يسجّل دخولاً ويتأكد من هبوط ناجح — كان سيمسك هذه الواقعة قبل وصولها لمستخدم
5

منهجية التدقيق والتحقق

Methodology & Verification

🧪 إعادة إنتاج حية

أنشأنا مستخدمَين تجريبيَّين معزولَين بنفس دور الواقعة، سجّلنا دخولهما عبر الخادم الحي، والتقطنا الحمولة الفعلية التي تسبب الحلقة — ثم حُذف الاثنان بعد انتهاء التدقيق (تحققنا: صفر مستخدمين تجريبيين متبقّين).

🔬 4 مسارات متوازية

محلل لآلية التعليق في الواجهة، محلل لمعمارية الصلاحيات في الخادم (مع فحص حي للأدوار الفعلية)، محلل لاستهلاك الصلاحيات وواجهات الأدوار، ومسار رابع عدائي بنى خريطة دورة الحياة ومصفوفة المشاكل بتحقق مستقل.

📍 دليل سطري

34 ملاحظة دليلية خام دُمجت بعد إزالة التكرار في 14 مشكلة مصنّفة (آلية التعليق: 6 · الخادم والفحص الحي: 9 · استهلاك الواجهة: 8 · المصفوفة العدائية: 11) — معظمها موثّق بملف وسطر محددين، وما تعذّر توثيقه سطرياً وُسم صراحة كنتيجة فحص حي.

🛡 مراجعة Codex مستقلة

خضع التقرير لمراجعة نموذج مستقل للتحقق من دقة الأدلة وعدالة التصنيف قبل الاعتماد.