41 KiB
41 KiB
سير عمل تسجيل الدخول في نظام Siro
توثيق شامل لتدفق المصادقة للمشرفين (Super Admin / Admin) وموظفي خدمة العملاء (Service Staff)
📋 فهرس المحتويات
- نظرة عامة على النظام
- المكونات الرئيسية
- سير عمل تسجيل حساب مشرف جديد
- سير عمل تسجيل الدخول (المشرف - siro_admin)
- سير عمل تسجيل الدخول (موظف الخدمة - siro_service)
- سير عمل إضافة الموظفين من قبل المشرف العام
- سير عمل تفعيل الحسابات المعلقة
- مقارنة بين تدفق siro_admin و siro_service
- الجوانب الأمنية (Security)
- قائمة الإصلاحات الأمنية المُنفَّذة
- الإجراءات الموصى بها للتحسين (Recommendations)
نظرة عامة على النظام
نظام Siro يستخدم بنية متعددة التطبيقات (Multi-App Architecture):
| التطبيق | الدور | ملفات المصادقة |
|---|---|---|
| siro_admin | المشرفون (Admin / Super Admin) | siro_admin/lib/views/auth/login_page.dart, otp_helper.dart |
| siro_service | موظفو خدمة العملاء | siro_service/lib/controller/login_controller.dart |
| Backend (PHP) | الخادم المركزي | backend/Admin/auth/login.php, backend/serviceapp/login.php |
المبدأ الأساسي: المصادقة متعددة العوامل (MFA) تعتمد على:
- بصمة الجهاز (Fingerprint) — يتم إنشاؤها في التطبيق وإرسالها مع كل طلب
- كلمة المرور — مشفرة باستخدام
password_hash - OTP عبر WhatsApp — للتحقق الإضافي (للمشرفين حصراً)
- JWT — التوكن النهائي للوصول
المكونات الرئيسية
تطبيق siro_admin (المشرفون)
| الملف | المسار | الوظيفة |
|---|---|---|
login_page.dart |
siro_admin/lib/views/auth/ |
واجهة تسجيل الدخول (كلمة مرور + هاتف اختياري) |
register_page.dart |
siro_admin/lib/views/auth/ |
واجهة طلب حساب مشرف جديد |
otp_helper.dart |
siro_admin/lib/controller/auth/ |
منطق OTP وجدولة إعادة الدخول (Auto Login) |
register_controller.dart |
siro_admin/lib/controller/auth/ |
منطق التسجيل |
تطبيق siro_service (خدمة العملاء)
| الملف | المسار | الوظيفة |
|---|---|---|
login_controller.dart |
siro_service/lib/controller/ |
منطق تسجيل الدخول مع OTP |
| (لا يوجد register) | التسجيل يتم عبر backend/serviceapp/register.php أو عبر add.php |
الباك إند (PHP)
| الملف | المسار | الوظيفة |
|---|---|---|
login.php |
backend/Admin/auth/ |
تسجيل دخول المشرف (الخطوة الأولى) |
verify_login.php |
backend/Admin/auth/ |
التحقق من OTP للمشرف (الخطوة الثانية) |
register.php |
backend/Admin/auth/ |
تسجيل مشرف جديد |
login.php |
backend/serviceapp/ |
تسجيل دخول موظف الخدمة |
register.php |
backend/serviceapp/ |
تسجيل موظف خدمة جديد |
add.php |
backend/Admin/Staff/ |
إضافة موظف/مشرف من قبل المشرف العام |
pending.php |
backend/Admin/Staff/ |
جلب الحسابات المعلقة |
activate.php |
backend/Admin/Staff/ |
تفعيل الحسابات المعلقة |
jwtService.php |
backend/Admin/jwtService.php |
إصدار JWT لخدمة العملاء (قديم) |
JwtService.php |
backend/core/Auth/ |
خدمة JWT الأساسية مع Authentication |
سير عمل تسجيل حساب مشرف جديد
📍 يبدأ من تطبيق siro_admin ← register_page.dart
[المستخدم] [التطبيق (Flutter)] [الباك إند (PHP)] [قاعدة البيانات]
| | | |
|── يدخل الاسم، الهاتف، | | |
| كلمة المرور | | |
|─────────────────────────────>| | |
| | | |
| |── قراءة بصمة الجهاز (fingerprint) | |
| | من `box.read('fingerprint')` | |
| | | |
| |── POST `/Admin/auth/register.php` | |
| | مع: name, phone, password, fingerprint | |
| |──────────────────────────────────────────>| |
| | | |
| | |── التحقق من القائمة البيضاء |
| | | `AUTHORIZED_ADMIN_PHONES` |
| | | في متغيرات البيئة (.env) |
| | | |
| | |── التحقق من التكرار: |
| | | phone OR fingerprint_hash |
| | | |
| | |── تشفير البيانات: |
| | | • name ← encryptData() |
| | | • phone ← encryptData() |
| | | • fingerprint ← encrypt() |
| | | • fingerprint_hash = |
| | | SHA-256(fingerprint) |
| | | • password ← password_hash |
| | | |
| | |── توليد UUID آمن: |
| | | bin2hex(random_bytes(16)) |
| | | |
| | |── INSERT INTO adminUser |
| | | status = 'pending' |
| | | role = 'admin' |
| | |─────────────────────────────>| (id, fingerprint, fingerprint_hash,
| | | | name, phone, password, role,
| | | | status='pending', created_at)
| | | |
| |<── { status: "pending", message: "..." } | |
| | | |
|<── رسالة "تم تقديم الطلب" | | |
ملاحظات أمنية حول التسجيل:
- ✅ القائمة البيضاء (
AUTHORIZED_ADMIN_PHONES) تمنع أي شخص من التسجيل دون إذن مسبق - ✅ الاسم والهاتف والبصمة مشفرة في قاعدة البيانات
- ✅ بصمة الجهاز محولة إلى SHA-256 Hash للبحث السريع
- ✅
bin2hex(random_bytes(16))لتوليد UUID آمن — تم إصلاحه منrand() - 🔴 الحساب ينشأ بحالة
pendingولا يمكنه الدخول حتى يتم تفعيله يدوياً
سير عمل تسجيل الدخول (المشرف - siro_admin)
📍 يبدأ من login_page.dart ← OtpHelper.loginWithPassword()
الخطوة الأولى: إرسال طلب الدخول
[المستخدم] [otp_helper.dart] [login.php (Backend)] [Redis / DB]
| | | |
|── يدخل كلمة المرور | | |
| (ورقم الهاتف لأول | | |
| مرة) | | |
|──────────────────────>| | |
| | | |
| |── POST /Admin/auth/login.php | |
| | payload: fingerprint, | |
| | password, phone (اختياري), | |
| | aud (اختياري), is_renewal | |
| |──────────────────────────────────>| |
| | | |
| | |── Rate Limiting: |
| | | 5 محاولات/دقيقة لكل IP |
| | | |
| | |── البحث بـ fingerprint_hash |
| | | (SHA-256 للبصمة) |
| | | |
| | [إذا لم يوجد بالبصمة والهاتف موجود] |
| | |── البحث بالهاتف المشفر |
| | | (لأول مرة أو جهاز جديد) |
| | | |
| | |── التحقق من status: |
| | | • pending → رفض مع رسالة |
| | | • suspended → رفض |
| | | • rejected → رفض |
| | | • active → متابعة |
| | | |
| | |── التحقق من كلمة المرور: |
| | | password_verify(password) |
| | | |
التفرع حسب is_renewal:
|
┌───────────────┴───────────────┐
| |
is_renewal=1 is_renewal=0 أو missing
(إعادة دخول تلقائي) (تسجيل دخول يدوي)
| |
| ├── توليد OTP عشوائي
| | (100000-999999) ← 6 أرقام
| |
| ├── فك تشفير رقم الهاتف
| | ← إرسال OTP عبر WhatsApp
| |
| ├── حفظ OTP مشفراً:
| | `token_verification_admin`
| | (phone مشفر، token مشفر،
| | expiration 10 دقائق)
| |
| └── رد: { status: "otp_required",
| phone: masked }
|
├── إلغاء التوكن القديم من Redis
| (Token Revocation)
|
├── توليد JWT جديد:
| generateAccessToken(id, role, aud, fingerprint)
|
└── رد مباشر: { jwt, admin, expires_in }
الخطوة الثانية (عند طلب OTP): التحقق ← verify_login.php
[otp_helper.dart] [verify_login.php] [DB / Redis]
| | |
|── POST /Admin/auth/ | |
| verify_login.php | |
| payload: otp, | |
| fingerprint, phone | |
|────────────────────────────>| |
| | |
| |── Rate Limiting: |
| | 3 محاولات OTP/5 دقائق لكل IP |
| | |
| |── البحث بالـ fingerprint_hash |
| | من جدول adminUser |
| | |
| |── تشفير OTP المدخل |
| | encryptData(otp) |
| | |
| |── البحث في token_verification: |
| | phone_number = encryptedPhone |
| | AND token = encryptedOtp |
| | AND expiration >= NOW() |
| | |
| [غير صالح أو منتهي] | |
|<── "رمز التحقق غير صالح" | |
| | |
| [صالح] | |
| |── حذف OTP (استخدام لمرة واحدة) |
| | DELETE FROM token_verification |
| | |
| |── إلغاء التوكن القديم من Redis |
| | |
| |── توليد JWT جديد: |
| | generateAccessToken(id, role, |
| | aud, fingerprint) |
| | |
|<── { jwt, admin, expires_in }| |
| | |
|── حفظ البيانات محلياً: | |
| • JWT ← box.write(jwt) | |
| • admin_id ← box.write | |
| • admin_role ← box.write | |
| • phoneVerified ← true | |
| • admin_password ← حفظ | |
| | |
|── Get.offAll(AdminHomePage())| |
مخطط الحالة الكامل للمشرف (State Machine):
┌───────────┐
│ Pending │ ← بعد التسجيل، بحاجة تفعيل
└─────┬─────┘
│ Activate.php (يتطلب JWT مع role=admin)
▼
┌─────────────────┐
│ Active │ ← يمكنه تسجيل الدخول
└────────┬────────┘
│
┌─────────┴──────────┐
│ │
▼ ▼
┌───────────┐ ┌───────────┐
│ Suspended │ │ Rejected │
└───────────┘ └───────────┘
سير عمل تسجيل الدخول (موظف الخدمة - siro_service)
📍 يبدأ من login_controller.dart ← login()
[LoginController] [serviceapp/login.php] [DB / Redis]
| | |
|── قراءة البصمة من | |
| box.read(fingerprint) | |
| | |
|── POST /serviceapp/login.php | |
| payload: fingerprint, | |
| password, email, | |
| aud = "service" | |
|────────────────────────────>| |
| | |
| |── Rate Limiting: |
| | 5 محاولات/دقيقة لكل IP |
| | |
| |── البحث بـ fingerprint_hash |
| | في `users` WHERE user_type |
| | = 'service' |
| | |
| [إذا لم يوجد, والإيميل موجود] |
| |── البحث بالإيميل المشفر |
| | |
| |── التحقق من status: |
| | • pending → "قيد المراجعة" |
| | • suspended → "معلق" |
| | • approved → متابعة |
| | |
| |── التحقق من كلمة المرور |
| | |
| |── فك تشفير البيانات للعرض: |
| | first_name, last_name, |
| | email, phone |
| | |
| |── إدارة التوكنات: |
| | 1. البحث عن توكن موجود في |
| | Redis (active_token) |
| | 2. إذا وجد وصالح ← استخدامه |
| | 3. إذا لم يوجد ← إلغاء القديم |
| | وتوليد جديد |
| | |
| |── توليد HMAC Key: |
| | hmac = hash_hmac(sha256, |
| | userId, SECRET_KEY_HMAC) |
| | |
|<── { message, data, jwt, | |
| hmac, expires_in } | |
| | |
|── إرسال OTP: | |
| POST /auth/otp/request.php | |
| payload: receiver=phone, | |
| user_type='service' | |
| | |
|── عرض Dialog OTP | |
| | |
|── التحقق من OTP: | |
| POST /auth/otp/verify.php | |
| payload: phone_number, | |
| token_code, user_type | |
| | |
|── حفظ JWT + HMAC محلياً | |
| | |
|── Get.offAll(Main()) | |
سير عمل إضافة الموظفين من قبل المشرف العام
📍 backend/Admin/Staff/add.php
يستخدم هذا الملف لإضافة موظفين جدد من قبل المشرف العام (Super Admin أو Admin فقط).
┌─────────────────────────────────────┐
│ add.php │
│ │
│ ✅ JWT Authentication: │
│ $jwtService = new JwtService │
│ $auth = $jwtService->authenticate│
│ $authRole = $auth->role │
│ if role ≠ super_admin || admin → │
│ رفض الطلب │
└──────────────────┬──────────────────┘
│
──────────────┼──────────────
role='admin' │ role='service'
│
┌─────────────────────────┴──────────────────────────┐
▼ ▼
┌────────────────┐ ┌──────────────────┐
│ adminUser │ │ users │
│ table │ │ table │
├────────────────┤ ├──────────────────┤
│ id (bin2hex) │ │ id (bin2hex) │
│ fingerprint │ │ fingerprint │
│ fingerprint_ │ │ fingerprint_hash │
│ hash │ │ phone (مشفر) │
│ name (مشفر) │ │ email (مشفر) │
│ phone (مشفر) │ │ gender │
│ password │ │ password │
│ role 'admin' │ │ birthdate │
│ created_at │ │ user_type │
└────────────────┘ │ 'service' │
│ first_name (مشفر) │
│ last_name │
│ site │
│ created_at │
└──────────────────┘
🔐 آلية التحقق في add.php (بعد الإصلاح):
$jwtService = new JwtService($redis);
$auth = $jwtService->authenticate(); // يقرأ Bearer token من Authorization header
$authRole = $auth->role ?? ''; // يستخرج الدور من JWT payload
if ($authRole !== 'super_admin' && $authRole !== 'admin') {
jsonError("غير مصرح لك. فقط المشرفون يمكنهم إضافة موظفين.");
exit;
}
نقاط مهمة في add.php:
- ✅
bin2hex(random_bytes(16))لتوليد UUID آمن - ✅ البيانات الحساسة مشفرة قبل الحفظ
- ✅
fingerprintاختياري — يمكن إضافة موظف بدون بصمة مسبقة - ✅ التحقق من JWT Authentication مطلوب قبل الإضافة
سير عمل تفعيل الحسابات المعلقة
📍 pending.php → activate.php
استعراض الحسابات المعلقة:
[pending.php] [قاعدة البيانات]
│ │
│── SELECT FROM adminUser │
│ WHERE status='pending' │
│ │
│── SELECT FROM users │
│ WHERE status='pending' │
│ AND user_type='service' │
│ │
│── فك تشفير الأسماء والأرقام │
│ │
│── دمج النتائج (array_merge) │
│ │
│<── { data: [all_pending] } │
تفعيل حساب:
[activate.php] [قاعدة البيانات]
│ │
│── التحقق من JWT: │
│ $jwtService = new JwtService │
│ $auth = $jwtService->authenticate│
│ $authRole = $auth->role │
│ if ≠ super_admin && ≠ admin → │
│ رفض │
│ │
│── POST مع: user_id, type │
│ │
│ type='admin': │
│ UPDATE adminUser │
│ SET status='active' │
│ WHERE id=user_id │
│ AND status='pending' │
│ │
│ type='service': │
│ UPDATE users │
│ SET status='approved' │
│ WHERE id=user_id │
│ AND status='pending' │
│ AND user_type='service' │
│ │
│<── "تم التفعيل بنجاح" │
🔐 آلية التحقق في activate.php (بعد الإصلاح):
$jwtService = new JwtService($redis);
$auth = $jwtService->authenticate();
$authRole = $auth->role ?? '';
if ($authRole !== 'super_admin' && $authRole !== 'admin') {
jsonError("غير مصرح لك. فقط المشرف العام يمكنه تفعيل الحسابات.");
exit;
}
مقارنة بين تدفق siro_admin و siro_service
| الخاصية | siro_admin (المشرف) | siro_service (خدمة العملاء) |
|---|---|---|
| جدول البيانات | adminUser |
users |
| مفتاح البحث | بصمة الجهاز ← الهاتف | بصمة الجهاز ← الإيميل |
| آلية OTP | OTP عبر WhatsApp كخطوة قبل JWT | JWT يصدر أولاً ثم OTP كخطوة تأكيد |
| Auto Login | Yes (is_renewal=1) + JWT غير منتهي | Yes (كلمة مرور مخزنة) |
| إلغاء التوكن القديم | قبل إصدار الجديد (Redis) | قبل إصدار الجديد (Redis) |
| عدد أرقام OTP | 6 أرقام (مُحسَّن) | لا يوجد OTP مدمج بالـ login |
| HMAC Key | غير مستخدم | يستخدم للتوافق مع CRUD |
| نوع التسجيل | تسجيل ذاتي + قائمة بيضاء | إضافة يدوية أو تسجيل ذاتي |
| الحالة عند الإنشاء | pending |
pending / active |
الجوانب الأمنية (Security)
✅ نقاط القوة
- تشفير البيانات الحساسة — جميع PII (الاسم، الهاتف، الإيميل، البصمة) مشفرة في قاعدة البيانات بواسطة
encryptData() - بصمة الجهاز (Fingerprint) — ربط الحساب بجهاز معين يمنع الوصول من أجهزة غير معروفة
- إلغاء التوكن (Token Revocation) — التوكن القديم يُلغى قبل إصدار الجديد عبر Redis
- OTP لمدة محدودة — صلاحية 10 دقائق فقط للرمز
- استخدام لمرة واحدة — OTP يُحذف بعد التحقق
- القائمة البيضاء — التسجيل الذاتي مقيد بأرقام مصرح بها من
.env - Hash للبصمة —
SHA-256للبحث السريع دون تخزين البصمة كما هي - قناع رقم الهاتف — في الاستجابة، يظهر فقط
07XX***XXX - UUID آمن —
bin2hex(random_bytes(16))بدلاً منrand()(تم الإصلاح) - JWT Authentication — لملفي add.php و activate.php (تم الإصلاح)
- Rate Limiting — على login و OTP (تم الإضافة)
✅ الإصلاحات الأمنية المُنفَّذة
| # | الثغرة | الملف | الحالة |
|---|---|---|---|
| 1 | rand() لتوليد ID (ضعيف) |
Admin/auth/register.php |
✅ تم الاستبدال بـ bin2hex(random_bytes(16)) |
| 2 | التحقق من الصلاحيات معلق (Commented Out) | Admin/Staff/add.php |
✅ تم التفعيل عبر JwtService::authenticate() |
| 3 | لا يوجد تحقق من صلاحية المشرف العام | Admin/Staff/activate.php |
✅ تم إضافة JWT + التحقق من role |
| 4 | لا يوجد Rate Limiting على login | Admin/auth/login.php |
✅ تم الإضافة (5 محاولات/دقيقة) |
| 5 | لا يوجد Rate Limiting على login | serviceapp/login.php |
✅ تم الإضافة (5 محاولات/دقيقة) |
| 6 | لا يوجد Rate Limiting على OTP | Admin/auth/verify_login.php |
✅ تم الإضافة (3 محاولات/5 دقائق) |
| 7 | OTP 5 أرقام (ضعيف) | Admin/auth/login.php |
✅ تم الرفع إلى 6 أرقام (100000-999999) |
❌ الثغرات المتبقية (لم تُعالَج بعد)
| الثغرة | الموقع | التأثير |
|---|---|---|
| كلمات مرور plain text (قديمة) | Admin/jwtService.php (السطر 49) |
دعم كلمات المرور غير المشفرة للتوافق القديم |
| JWT Service مكرر | Admin/jwtService.php و core/Auth/JwtService.php |
يوجد ملفان باسم JwtService بوظائف مختلفة |
| Auto Login بدون OTP | Admin/auth/login.php مع is_renewal=1 |
إذا سُرقت البصمة وكلمة المرور، يمكن الدخول بدون OTP |
| كشف البصمة في الأخطاء | otp_helper.dart |
لا يوجد تدقيق كافٍ في التقاط الأخطاء |
قائمة الإصلاحات الأمنية المُنفَّذة
1. ✅ Admin/auth/register.php — استبدال ID الضعيف
قبل الإصلاح:
$uniqueId = rand(100000000, 999999999); // غير آمن، قد يتكرر
بعد الإصلاح:
$uniqueId = bin2hex(random_bytes(16)); // UUID آمن (32 حرف hex عشوائي)
2. ✅ Admin/Staff/add.php — تفعيل التحقق من الصلاحيات
قبل الإصلاح:
// التحقق معلق (Commented Out)
// $auth = JwtService::authenticate($redis);
// if ($auth['role'] !== 'super_admin' && $auth['role'] !== 'admin') { ... }
بعد الإصلاح:
$jwtService = new JwtService($redis);
$auth = $jwtService->authenticate();
$authRole = $auth->role ?? '';
if ($authRole !== 'super_admin' && $authRole !== 'admin') {
jsonError("غير مصرح لك. فقط المشرفون يمكنهم إضافة موظفين.");
exit;
}
3. ✅ Admin/Staff/activate.php — إضافة JWT Authentication
قبل الإصلاح:
// يجب التأكد من صلاحيات الـ Super Admin هنا
// (عادةً يتم التحقق من التوكن أو الـ Session)
بعد الإصلاح:
$jwtService = new JwtService($redis);
$auth = $jwtService->authenticate();
$authRole = $auth->role ?? '';
if ($authRole !== 'super_admin' && $authRole !== 'admin') {
jsonError("غير مصرح لك. فقط المشرف العام يمكنه تفعيل الحسابات.");
exit;
}
4. ✅ Admin/auth/login.php — إضافة Rate Limiting + رفع OTP إلى 6 أرقام
// Rate Limiting قبل بدء المعالجة
$rateLimiter = new RateLimiter($redis);
$rateLimiter->enforce(RateLimiter::identifier(), 'login');
// ... لاحقاً عند توليد OTP ...
$otp = rand(100000, 999999); // 6 أرقام بدلاً من 5
5. ✅ serviceapp/login.php — إضافة Rate Limiting
$rateLimiter = new RateLimiter($redis);
$rateLimiter->enforce(RateLimiter::identifier(), 'login');
6. ✅ Admin/auth/verify_login.php — إضافة Rate Limiting لـ OTP
$rateLimiter = new RateLimiter($redis);
$rateLimiter->enforce(RateLimiter::identifier(), 'otp'); // 3 محاولات/5 دقائق
خلاصة
graph TD
subgraph "siro_admin (تطبيق المشرف)"
A[Login Page] --> B[OtpHelper.loginWithPassword]
B --> C{is_renewal?}
C -->|نعم| D[JWT مباشر]
C -->|لا| E[طلب OTP - 6 أرقام]
E --> F[Verify OTP - Rate Limited]
F --> G[JWT نهائي]
end
subgraph "Backend PHP - مؤمَّن"
H[Admin/auth/login.php] --> RL1[Rate Limiter ✅]
RL1 --> I[بحث بالبصمة/هاتف]
I --> J[فحص الحالة]
J --> K[التحقق من كلمة المرور]
K --> L[توليد OTP 6-digits ✅ / JWT]
M[verify_login.php] --> RL2[Rate Limiter (OTP) ✅]
RL2 --> N[التحقق من OTP]
N --> O[إصدار JWT]
P[add.php] --> AUTH1[JWT Authentication ✅]
Q[activate.php] --> AUTH2[JWT Authentication ✅]
end
subgraph "siro_service (تطبيق الخدمة)"
R[LoginController.login] --> S[serviceapp/login.php]
S --> RL3[Rate Limiter ✅]
RL3 --> T[JWT + HMAC]
T --> U[OTP للتأكيد]
U --> V[تسجيل دخول نهائي]
end
B --> H
F --> M
R --> S
النظام مبني على أساس أمني قوي مع تشفير متعدد الطبقات. تم إصلاح 7 ثغرات أمنية:
- UUID آمن بدلاً من
rand()في تسجيل المشرفين - JWT Authentication في add.php — يمنع أي شخص غير مصرح له من إضافة موظفين
- JWT Authentication في activate.php — يضمن أن المشرف العام فقط هو من يمكنه التفعيل
- Rate Limiting (5 محاولات/دقيقة) على login.php للمشرفين
- Rate Limiting (5 محاولات/دقيقة) على login.php لخدمة العملاء
- Rate Limiting (3 محاولات/5 دقائق) على verify_login.php (OTP)
- رفع OTP إلى 6 أرقام لزيادة صعوبة التخمين
تاريخ التوثيق: 12 يونيو 2026
آخر تحديث: 12 يونيو 2026 (تم تنفيذ الإصلاحات)
الإصدار: 2.0
المراجعة القادمة: —
المعنيون: فريق التطوير — Siro