المستوى المطلوب: متوسط. محتاج تكون مرتاح مع Express وفكرة الـ middleware، ومعاك طبقة مصادقة (JWT أو session) بتحدد المستخدم الحالي. لو لسه مبتدئ في Node، ابدأ بمقال بناء أول REST API بـ Express قبل ده.
هتخرج من المقال ده بنظام صلاحيات RBAC شغّال في Node.js، بيحدد مين يقدر يعمل إيه من مكان واحد، من غير ما تنثر شروط if في كل مسار. الفحص نفسه بياخد أقل من مللي ثانية، وإضافة دور جديد بتبقى تعديل سطر واحد بدل ما تلف على 40 ملف.
اعمل نظام صلاحيات (RBAC) في Node.js من الصفر
المشكلة باختصار
الطريقة الشائعة إنك تكتب if (user.isAdmin) { ... } جوّه كل route. دي بتفشل بسرعة. أول ما ييجي دور تالت اسمه "محرّر" بصلاحيات في النص، بتلاقي نفسك بتعدّل نفس الشرط في عشرات المسارات، وتنسى واحد، وتفتح ثغرة. ده بالظبط السبب إن "التحكم المكسور في الوصول" (Broken Access Control) طلع أخطر ثغرة في تصنيف OWASP لسنة 2021.
الحل مش شرط أذكى، الحل إنك تفصل القرار عن المكان. تحطّ "مين يقدر يعمل إيه" في جدول واحد، وتفرضه بنقطة فحص واحدة.
الفكرة ببساطة: بطاقات دخول المبنى
تخيّل مبنى شركة. مش كل موظف معاه مفتاح لكل باب. الاستقبال بياخد بطاقة "زائر" تفتح البوابة الرئيسية بس. الموظف العادي بطاقته تفتح مكتبه والمطبخ. المدير بطاقته تفتح كل حاجة. لاحظ إن الأبواب مش مربوطة بأسماء الناس، هي مربوطة بنوع البطاقة. لو عايز تغيّر صلاحيات كل الموظفين، بتعدّل إعداد البطاقة مرة واحدة، مش بتلف على كل موظف.
البطاقة هي الدور (Role)، والأبواب اللي بتفتحها هي الصلاحيات (Permissions). ده هو RBAC بالظبط.
علميًا، الـ RBAC اختصار Role-Based Access Control، وهو نموذج بيربط ثلاث طبقات بالترتيب: المستخدم ← الدور ← الصلاحية. المستخدم مبياخدش صلاحيات مباشرة، بياخد دور، والدور بيحمل مجموعة صلاحيات. الفصل ده بيخلّي أي تعديل في صلاحيات دور يسري فورًا على كل المستخدمين اللي عليه. النموذج موصّف رسميًا في معيار NIST/INCITS 359.
الخطوات
- عرّف مصفوفة الصلاحيات: جدول واحد يربط كل دور بالصلاحيات المسموح بيها.
- اكتب دالة فحص واحدة:
can(role, permission)ترجّع true/false. - غلّفها في middleware:
requirePermission()يقف قدام أي مسار. - اربط كل مسار بالصلاحية اللي يحتاجها.
1) مصفوفة الصلاحيات
سمّي الصلاحيات بصيغة مورد:فعل (زي post:delete) عشان تفضل مقروءة وقابلة للتوسّع. وحوّل كل مجموعة لـ Set عشان الفحص يبقى في زمن ثابت O(1) مهما كبر عدد الصلاحيات.
// permissions.js
const ROLES = {
admin: ['post:read', 'post:write', 'post:delete', 'user:manage'],
editor: ['post:read', 'post:write'],
viewer: ['post:read'],
};
// كل دور يتحوّل لـ Set عشان الفحص O(1) مش O(n)
const rolePerms = Object.fromEntries(
Object.entries(ROLES).map(([role, perms]) => [role, new Set(perms)])
);
function can(role, permission) {
return rolePerms[role]?.has(permission) ?? false;
}
module.exports = { can, ROLES };