طَبِّقْهاتعلّم المبدأ، وطبّقه بيدكمقرر 243 · أمن الويب
الدرس 27 · الوحدة 12: التحقق من الإدخال والوصول المصرح به

التحقق من المدخلات بقائمة المسموح

البوابة الأولى: ماذا تقبل، وكيف تمنع ما لم تتوقّعه من التسلل؟ ⏱ نحو 40 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • تفرّق بين التحقق والتطهير.
  • تتحقّق بقائمة سماح بالنوع والشكل والمدى.
  • تمنع الإسناد الجماعي باختيار الحقول بالاسم.
  • تضع التحقق في الخادم.

١التحقق أولًا، قبل كل شيء

التحقق من المدخلات هو البوابة الأولى: يرفض ما لا يطابق المتوقَّع قبل أن يصل إلى منطقك أو قاعدتك. يُكمّل الترميز والفصل (الدرس 4): هذان يحميان عند الإخراج، والتحقق يحمي عند الدخول.

التحقق Validationالتطهير/الترميز
يفعليقبل أو يرفضيحوّل ليصبح آمنًا
المكانعند الدخولعند الإخراج لكل سياق
المثالالعمر عدد 0–120ترميز < في HTML

القاعدة الأهم: تحقّق بـقائمة سماح (ما المسموح؟) لا بقائمة منع (ما الممنوع؟). قائمة المنع تنسى دائمًا حالة، أما قائمة السماح فترفض كل ما لم تُدرجه صراحة.

٢تحقق بالنوع والشكل والمدى

لكل حقل عقد: نوعه، وشكله، ومداه، وقائمة قيمه إن كانت محدودة. انظر تحققًا كاملًا، ثم جرّب كسره:

const express = require('express');const app = express();app.use(express.json());app.use(express.urlencoded({ extended: false })); const ROLES = ['customer', 'staff'];        // قائمة سماح app.post('/users', (req, res) => {  const { name, age, role } = req.body;  // تحقق بالنوع والشكل والمدى وقائمة السماح  if (typeof name !== 'string' || name.trim().length < 2 || name.length > 50)    return res.status(400).json({ error: 'الاسم من 2 إلى 50 حرفًا' });  if (!Number.isInteger(age) || age < 0 || age > 120)    return res.status(400).json({ error: 'العمر عدد صحيح بين 0 و 120' });  if (!ROLES.includes(role))    return res.status(400).json({ error: 'دور غير مسموح' });  // نبني الكائن بحقول مختارة بالاسم (ضد الإسناد الجماعي)  res.status(201).json({ created: { name: name.trim(), age, role } });}); app.listen(3000);

الأول صحيح (201). والبقية رُفضت 400: اسم قصير، وعمر خارج المدى، ودور ليس في قائمة السماح. كل حقل يُفحص بعقده، وما يخالف يُرفض فورًا.

البُعدالسؤالمثال
النوعنص؟ عدد؟ منطقي؟typeof age === "number"
الشكليطابق النمط؟بريد، هاتف، تاريخ
المدىضمن الحدود؟0 <= age <= 120
قائمة السماحمن القيم المسموحة؟ROLES.includes(role)

٣الإسناد الجماعي ومكان التحقق

خطر خفيّ: نسخ جسم الطلب كله { ...req.body }. فيرسل المهاجم حقلًا لم تتوقّعه (role: "admin" أو status: "paid") فيدخل في كائنك. الدفاع: اختر الحقول المسموحة بالاسم، ولا تنسخ الجسم كله:

وفي العمل الحقيقي تُستخدم مكتبات تحقّق (schemas) تصف عقد كل حقل مرة وتفرضه، بدل كتابة الشروط يدويًّا لكل مسار.

توقّع وتحقّق

التحقق من المدخلات يحدث:

الأقوى:

نسخ ...req.body يسبب:

مكان التحقق الموثوق:

الخلاصة

تحدٍّ برمجي: تحقّق من التذاكر

متقدم

مسار إنشاء التذكرة يقبل أي حقول وينسخ الجسم كله (إسناد جماعي) بلا تحقق. أصلحه: تحقّق من العنوان والأولوية، واختر الحقول بالاسم.

المطلوب:
  • العنوان نص من 3 إلى 100 حرف، وإلا 400.
  • الأولوية من ['low', 'normal', 'high']، وإلا 400.
  • ابنِ التذكرة بحقول مختارة بالاسم، لا بنسخ الجسم.
👀 اعرض الاستجابات المطلوبة
✓ التحقق الآلي: يشغّل المختبر خادمك من جديد ويرسل إليه هذه الطلبات بالترتيب ثم يفحص الردود
  1. POST /tickets {"title": "لا يعمل الدفع", "priority": "high"}
  2. POST /tickets {"title": "لا", "priority": "high"}
  3. POST /tickets {"title": "عطل", "priority": "urgent"}
  4. POST /tickets {"title": "محاولة", "priority": "low", "status": "closed", "isAdmin": true}
  • الخادم يعمل ويرد على كل الطلبات بلا أخطاء غير معالَجة
  • تذكرة صحيحة ← 201
  • عنوان قصير ← 400
  • أولوية غير مسموحة ← 400
  • حالة التذكرة open رغم ما أُرسل
  • لا نسخ لجسم الطلب كله

💡 تلميح 1
تحقّق: if (typeof title !== 'string' || title.trim().length < 3 …) return res.status(400)….
💡 تلميح 2
الأولوية: if (!PRIORITIES.includes(priority)) ….
💡 تلميح 3
ابنِ الكائن بالحقول المختارة: { title: title.trim(), priority, status: 'open' }.

حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.

const express = require('express');const app = express();app.use(express.json());app.use(express.urlencoded({ extended: false })); const PRIORITIES = ['low', 'normal', 'high'];app.post('/tickets', (req, res) => {  const { title, priority } = req.body;  if (typeof title !== 'string' || title.trim().length < 3 || title.length > 100)    return res.status(400).json({ error: 'العنوان من 3 إلى 100 حرف' });  if (!PRIORITIES.includes(priority))    return res.status(400).json({ error: 'أولوية غير مسموحة' });  const ticket = { title: title.trim(), priority, status: 'open' };   // حقول مختارة بالاسم  res.status(201).json({ ticket });});app.listen(3000);
انتقل إلى تقييم الدرساختيار من متعدد، وصح وخطأ، وأكمل الفراغ، وتتبّع الناتج.ابدأ التقييم ←

📝 معلم؟ أرسل ملاحظة للمؤلف عن هذا الدرس