مدير الأكاديمية

التدقيق واختبار الضغط

تدقيق نسخة الويب واختبار ضغطها

مراجعة كاملة لنسخة الويب في سبتمبر ٢٠٢٦؛ الأمن والصحة والأداء، مع الإصلاحات التي نتجت عنها والأرقام التي قاستها.

الخلاصة

رُوجعت نسخة الويب من طرف إلى طرف في ٥ سبتمبر ٢٠٢٦: مراجعة أمنية على مستوى الشيفرة، ومراجعة للصحة مقابل قواعد تطبيق الجوال، واختبار ضغط بأكاديمية من ٢٬٠٠٠ متدرب وحتى ١٠٠ مستخدم متزامن. لم يُعثر على أي خلل في سلامة البيانات أو الأمن. أنتجت المراجعة تسعة تحسينات (مذكورة أدناه) دُمجت كلها، ومجموعة حدود مقاسة يعكسها دليل النشر الآن.

المجال النتيجة
اختبارات الوحدة (الدفتر، الرصيد، الإحالات، الأرشيف، النسخ الاحتياطي، التشفير، التقويم) ٣٧ / ٣٧ ناجحة
مسار HTTP شامل لكل التدفقات ٢٠ / ٢٠ خطوة ناجحة
فحص الأنواع (svelte-check) ٠ أخطاء
تدقيق الاعتماديات (npm audit --omit=dev) ٠ ثغرات في اعتماديات الإنتاج
اختبار الحمل، عملية واحدة، ٢٥ إلى ١٠٠ مستخدم متزامن ٠ أخطاء، نحو ٤٠ صفحة/ث
اختبار الحمل، ٤ عمليات، ١٠٠ مستخدم متزامن ٠ أخطاء، ١٢٧ صفحة/ث

النطاق والمنهج

  • مراجعة الشيفرة لكل وحدة في الخادم: المصادقة، إدارة الجلسات، حماية الطلبات، إجراءات النماذج، تقديم الملفات، استيراد/تصدير النسخ الاحتياطي، طبقة SQLite وصورة Docker.
  • مقارنة قاعدة بقاعدة مع مواصفات تطبيق الجوال (معادلة الرصيد، الثابت الذهبي لـ final_amount، استهلاك رصيد الجلسات، سلاسل الأرشفة، مفاتيح إزالة تكرار التنبيهات، حدود الإحالة، غلاف النسخ الاحتياطي).
  • فحوصات آلية: ٣٧ اختبار وحدة بـ Vitest على قاعدة بيانات مؤقتة، واختبار دخان HTTP من ٢٠ خطوة يقود كل شاشة عبر إجراءات النماذج نفسها التي يستخدمها المتصفح، وsvelte-check، وnpm audit.
  • اختبار الضغط: أكاديمية من ٢٬٠٠٠ متدرب مُلئت عبر HTTP، ثم مزيج موزون من تحميلات صفحات حقيقية وعمليات كتابة من ٢٥ و٥٠ و١٠٠ مستخدم افتراضي، ٣٠ ثانية لكل منها، على حاسوب محمول واحد شغّل مولد الحمل أيضًا (لذا كل الأرقام أدناه متحفظة).

المراجعة الأمنية

الفحص الحالة
المصادقة مطلوبة لكل صفحة وملف وتنزيل؛ التشغيل الأول مقيَّد بإنشاء الحساب منفَّذ
كلمات المرور مجزّأة بـ scrypt (N = 16384)، ملح عشوائي، مقارنة بزمن ثابت منفَّذ
رموز الجلسات مخزَّنة مجزّأة (SHA-256)؛ httpOnly وSameSite=Lax وSecure على HTTPS؛ انتهاء منزلق ٣٠ يومًا؛ إنهاء كل الجلسات عند تغيير كلمة المرور منفَّذ
حد محاولات الدخول: ١٠ إخفاقات لكل عنوان كل ١٥ دقيقة منفَّذ
CSRF: فحص الأصل في SvelteKit لكل إرسال نموذج (ORIGIN / ترويسات الوكيل) منفَّذ
سياسة أمن المحتوى (CSP) بـ nonce لكل طلب: السكربتات من أصل التطبيق فقط، لا سكربتات مضمّنة، لا اتصالات خارجية أُضيف في هذا التدقيق
ترويسات الأمان: nosniff وframe-ancestors/X-Frame-Options وسياسة المُحيل وسياسة الأذونات وHSTS في الإنتاج منفَّذ
SQL: كل العبارات ذات معاملات؛ لا يصل أي إدخال مستخدم إلى نص SQL مُتحقَّق منه
XSS: يهرّب Svelte كل الاستيفاءات؛ لا {@html} في أي مكان مُتحقَّق منه
اجتياز المسار: أسماء ملفات الصور والنسخ الاحتياطية تُتحقق وتُحلّ داخل مجلد البيانات مُتحقَّق منه بالاختبار
Zip-slip عند الاستعادة: تُرفض المدخلات ذات .. أو المسارات المطلقة أو اسم مفتاح الجهاز مُتحقَّق منه بالاختبار
التحقق عند الاستعادة: فحص السلامة والمخطط قبل استبدال قاعدة البيانات الحية؛ تراجع تلقائي عند الفشل مُتحقَّق منه بالاختبار
حدود الرفع: ٦٤ ميغابايت افتراضيًا (MAX_UPLOAD_MB، BODY_SIZE_LIMIT) منفَّذ
تشفير النسخ الاحتياطي: PBKDF2-SHA256 بـ ٢١٠ ألف دورة، AES-256-CBC، تحقق HMAC-SHA256 قبل فك التشفير؛ الإصدار القديم v1 مقروء مُتحقَّق منه بالاختبار
كلمة مرور النسخ التلقائي مشفّرة في التخزين بمفتاح خارج قاعدة البيانات وخارج كل نسخة احتياطية منفَّذ
الحسابات والجلسات في قاعدة بيانات منفصلة، لا تدخل النسخة الاحتياطية أبدًا منفَّذ
الحاوية تعمل بمستخدم غير جذر، حجم تخزين واحد، فحص صحة منفَّذ
لا تُسجَّل الأسرار أبدًا مُتحقَّق منه

ملاحظات متبقية: يوجد دور مدير واحد (لا أذونات لكل مستخدم)، ولا تسجيل دخول بعاملين، ولا TLS مدمج (يُنهى HTTPS عند الوكيل، وهو ما يفعله Coolify).

مراجعة الصحة

تُتُبِّعت كل قاعدة في تطبيق الجوال إلى شيفرة الويب وغُطيت باختبار:

  • الرصيد = Σالفواتير − Σالخصومات − Σالإحالة − Σالدفعات + Σالتسويات − Σالمسترد؛ الصفوف الملغاة تُحتسب صفرًا؛ لكل عملة.
  • enrollment.final_amount يساوي دائمًا الدفتر الحي لذلك التسجيل؛ يُعاد حسابه بعد كل إدراج وتعديل وإلغاء واستعادة؛ والتسجيل الذي أُلغيت كل صفوفه يُعاد حسابه إلى الصفر.
  • الخصم اليدوي لا يتجاوز الرسوم أبدًا (النموذج والمستودع).
  • الحاضر والمتأخر يستهلكان رصيد جلسة؛ الغائب والمعذور لا؛ تغيير الحالة يعيد الرصيد أو يخصمه.
  • إلغاء التسجيل يلغي الفاتورة والخصم ورصيد الإحالة ويُبقي الدفعات، فيصبح المتدرب دائنًا.
  • سلاسل الأرشفة تسم الأبناء؛ الاستعادة تعيد أولئك فقط؛ استعادة صف تستعيد فصله المؤرشف.
  • الحذف النهائي يتطلب سجلًا مؤرشفًا ويكتب الأرصدة النهائية في سجل التدقيق؛ حذف الفصل والصف يلغي صفوف الدفتر ويفصلها.
  • حدود الإحالة تقيّد الرصيد بالمبلغ المستحق ولا تُستهلك عندما لا يمكن منح شيء؛ تُرفض الإحالات الذاتية والدائرية.
  • تُزال تكرارات التنبيهات بالمفتاح؛ تنبيهات الرصيد المنخفض مفتاحها العدد المتبقي.
  • النسخ الاحتياطية تذهب وتعود بايتًا ببايت مع تنسيق الجوال؛ تُرفض كلمات المرور الخاطئة والبايتات المعدَّلة.

قرار تصميمي واحد بقي دون تغيير وموثَّق: نوع الصف refund يقلّل الرصيد كالدفعة ولا تنشئه أي شاشة.

التحسينات المنجزة خلال التدقيق

  1. سياسة أمن المحتوى بـ nonce (script-src 'self').
  2. تقسيم قائمة المتدربين إلى صفحات (٦٠ في الصفحة) مع عدّ كامل؛ وتقسيم الإشعارات (٥٠ في الصفحة).
  3. تقييد قوائم لوحة التحكم بما يُعرض؛ وإعادة كتابة مرشح المدين من استعلام فرعي مترابط إلى تمريرة مجمَّعة واحدة على الدفتر.
  4. عدّ متدربي قائمة صفوف الحضور في استعلام مجمَّع واحد بدلًا من استعلام لكل صف.
  5. صفحات الصف والفصل والإحالات تحمّل فقط أسماء المتدربين التي تعرضها.
  6. تخزين الإعدادات في الذاكرة لثانية (١٤ استعلامًا أقل لكل طلب).
  7. ترويسة X-Response-Time على كل استجابة للعمليات.
  8. وضع متعدد العمليات اختياري (WORKERS=n|auto)؛ يعمل المجدوِل في عملية واحدة فقط.
  9. علامة Secure لملف تعريف الارتباط تتبع بروتوكول الطلب؛ والتشغيل المحلي يشتق ORIGIN تلقائيًا.

اختبار الضغط

مجموعة البيانات

الجدول الصفوف
المتدربون ٢٬٠٠٠
التسجيلات ٢٬٦٦٧
صفوف الدفتر ٦٬٣٢٥
جلسات / سجلات الحضور ٤٨٠ / ٣٢٬٠٠٤
الإشعارات ٥٬٧٢٥
قيود التدقيق ٥٬٦٥٨
الملاحظات ١٬٨٣٧
حجم قاعدة البيانات ٢٥ ميغابايت

استغرق ملء ٢٬٠٠٠ متدرب وتسجيلاتهم ودفعاتهم و٤٨٠ كشف حضور عبر HTTP ٢٧ ثانية.

زمن الخادم لطلب واحد (وسيط ٥ محاولات)

الصفحة ملّي ثانية خادم
لوحة التحكم ٣٦٫٦
قائمة المتدربين (الكل) ٩٫٣
قائمة المتدربين (المدينون) ٢٣٫٧
بحث المتدربين ٨٫٥
تبويبات ملف المتدرب ٣ إلى ٦
الصفوف ٩٫٤
صفحة الصف ٢٤٫١
الفصول الدراسية ٥٫٩
قائمة صفوف الحضور ١٥٫٨
كشف الحضور ١٤٫٦
الإشعارات ١٣٫٥
سجل التدقيق ١٠٫٣
صفحة النسخ الاحتياطي ٥٫٨

الحمل المتزامن، ٣٠ ثانية لكل تشغيل

العمليات المستخدمون صفحة/ث أخطاء p50 p95 p50 الخادم p95 الخادم
١ ٢٥ ٤٠٫٣ ٠ ٦١٦ ms ٨٠٥ ms ١٥ ms ٦٨ ms
١ ٥٠ ٣٩٫٧ ٠ ١٬٢٢٥ ms ١٬٥٦٤ ms ١٥ ms ٦٩ ms
١ ١٠٠ ٤١٫٨ ٠ ٢٬٣٢٨ ms ٢٬٨٦٤ ms ١٥ ms ٧٠ ms
٤ ١٠٠ ١٢٧٫٤ ٠ ٤٧٥ ms ١٬٩٣١ ms ١٨ ms ٩٠ ms

كان المزيج ٢٠٪ لوحة تحكم، ٢٠٪ قائمة متدربين بمرشحات وبحث، ٢٥٪ تبويبات ملف، ٨٪ صفوف وفصول، ١٠٪ كشوف حضور، ٥٪ إشعارات، ١٢٪ كتابة (ملاحظات ودفعات). ذاكرة العملية الواحدة: ١٩٩ ميغابايت قبل التشغيل، ٣٣٠ ميغابايت بعده. صفر أخطاء من جهة الخادم في كل تشغيل.

قراءة الأرقام

  • بعملية واحدة يجيب الخادم نفسه في نحو ١٥ ملّي ثانية وسيطًا؛ والكمون من جهة العميل عند ١٠٠ مستخدم هو اصطفاف أمام نواة واحدة. أربعون صفحة في الثانية تعني نحو ٢٬٤٠٠ مشاهدة في الدقيقة، وهو أبعد بكثير من حركة أكاديمية واحدة.
  • أربع عمليات على الحاسوب نفسه ضاعفت الإنتاجية ثلاث مرات إلى ١٢٧ صفحة/ث بينما كان مولد الحمل ينافس على المعالج نفسه. على مضيف مخصص، اضبط WORKERS=auto.
  • تعامل SQLite في وضع WAL مع مزيج الكتابة (ملاحظات، دفعات، حضور) دون أخطاء انشغال.

التوصيات

  • انشر خلف HTTPS (وكيل Coolify) واضبط ORIGIN؛ وأبقِ TZ صحيحة ليتوافق النسخ اليومي و«اليوم» مع ساعتك.
  • اضبط WORKERS=auto على المضيفات ذات أكثر من نواة.
  • خذ النسخ الاحتياطية المشفّرة للتطبيق بانتظام والتقط لقطة لحجم /data؛ واختبر استعادة مرة واحدة.
  • احفظ كلمة مرور المدير في مدير كلمات مرور؛ وبدّلها من الإعدادات → الحساب عند تغيّر الموظفين.