23 KiB
23 KiB
تحليل تدفق تسجيل ودخول السائق (Siro Driver)
تصفُّل كامل من Flutter إلى PHP Backend
1. الملفات المشاركة
تطبيق Flutter — siro_driver
| الملف | الدور |
|---|---|
controller/auth/captin/login_captin_controller.dart |
كل منطق تسجيل الدخول (Google, Credentials, JWT, OTP) |
controller/auth/captin/register_captin_controller.dart |
كل منطق التسجيل (OTP, Verify, إرسال البيانات) |
controller/auth/captin/opt_token_controller.dart |
OTP Token |
controller/functions/crud.dart |
HTTP client مع NetGuard + JWT refresh |
controller/functions/encrypt_decrypt.dart |
تشفير/فك تشفير محلي |
controller/functions/device_info.dart |
Fingerprint |
Backend PHP
| الملف | الرابط المستخدم من التطبيق |
|---|---|
auth/captin/loginFromGoogle.php |
loginFromGoogleCaptin |
auth/captin/loginUsingCredentialsWithoutGoogle.php |
loginUsingCredentialsWithoutGoogle |
loginFirstTimeDriver.php |
loginFirstTimeDriver |
loginJwtDriver.php |
loginJwtDriver |
auth/captin/login.php |
تسجيل دخول عادي (email + phone + password) |
auth/Tester/ |
getTesterApp, updateTesterApp |
auth/otp/ |
OTP APIs |
2. التدفق الكامل — تسجيل حساب جديد (Sign Up)
🧩 مسار السجلات: من البداية حتى النهاية
┌─────────────────────────────────────────────────────────────────────────┐
│ التطبيق (Flutter) │ الباك إند (PHP) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. onInit() │
│ ├── getJWT(): │
│ │ POST /loginFirstTimeDriver.php │
│ │ payload: { id: AK.newId, password: AK.passnpassenger, │
│ │ aud: "${AK.allowed}$dev", fingerPrint } │
│ │ ← يحصل على registration JWT (صلاحية 450 ثانية) │
│ │ └── يخزّن JWT في box + secure storage │
│ │ │
│ 2. تسجيل الدخول عبر Google (loginWithGoogleCredential) │
│ ├── GET /auth/captin/loginFromGoogle.php?driverID=X │
│ │ └── هذا الملف يستخدم connect.php (قديم) ← لا JWT check │
│ │ ← إذا found → يقرأ data من الـ DB │
│ │ ← يخزّن بيانات كاملة (firstName, lastName, phone, gender, │
│ │ carYear, model, bankCode, ...) في GetStorage │
│ │ ← يفحص driver token (getDriverToken) ← يقارن مع المخزّن │
│ │ ← إذا تغيّر fingerprint → يحوّل لصفحة OTP │
│ │ ← ينتقل لـ HomeCaptain │
│ └── إذا not found → isPhoneVerified() → RegistrationView │
│ │
│ 3. التسجيل (sendOtpMessage) │
│ ├── POST /auth/otp/sendVerifyOtpMessage │
│ │ payload: { phone_number, driverId, email } │
│ ├── POST /auth/otp/verifyOtpDriver (للسائق) │
│ │ payload: { phone_number, token_code } │
│ │ ← يخزّن phoneDriver, phoneVerified = 1 │
│ │ ← ينتقل لـ RegistrationView │
│ │ │
│ 4. الرفع للمستندات والصور (CarLicense, ID Card, إلخ) │
│ ├── POST /addLicense (رخصة القيادة) │
│ ├── POST /addRegisrationCar (تسجيل السيارة) │
│ └── CRUD.post /signUpCaptin (إنشاء الحساب النهائي) │
│ payload: { first_name, last_name, email, phone, password, │
│ gender, site, birthdate } │
│ ← يخزّن driverID, dob, sex, phone │
│ ← يرسل sendVerifyEmail (OTP للبريد) │
│ ← ينتقل لـ VerifyEmailCaptainPage │
│ │
│ 5. بعد التحقق من الإيميل → LoginCaptin (يعيد تسجيل الدخول) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
3. التدفق الكامل — تسجيل الدخول (Login)
مسار تسجيل الدخول بالإيميل وكلمة المرور
┌────────────────────────────────────────────────────────────────────┐
│ Flutter │ PHP Backend │
├────────────────────────────────────────────────────────────────────┤
│ │
│ loginUsingCredentialsWithoutGoogle(email, password): │
│ │
│ GET /auth/captin/loginUsingCredentialsWithoutGoogle.php │
│ ?email=...&password=... │
│ │
│ ← هذا الملف يستخدم connect.php (قديم، لا JWT check) │
│ │
│ خطوات الباك إند: │
│ 1. filterRequest('email') │
│ 2. تشفير الإيميل: encryptData($email) │
│ 3. SELECT من driver LEFT JOIN phone_verification │
│ WHERE email = :encryptedEmail │
│ AND phone_verification.is_verified = '1' │
│ 4. password_verify($password, $data['password']) │
│ 5. فك تشفير جميع الحقول (phone, email, gender, birthdate, ...) │
│ 6. إرجاع JSON { status: "success", data: {...} } │
│ │
│ في التطبيق: │
│ 1. يخزّن كل البيانات في box (driverID, email, phone, ...) │
│ 2. يخزّن fingerprint في secure storage │
│ 3. يحصل على driver token (getDriverToken) │
│ 4. يقارن الـ token مع المخزّن ← إذا تغير → Dialog "new device" │
│ 5. إذا matched → Get.off(HomeCaptain) │
│ │
└────────────────────────────────────────────────────────────────────┘
مسار تسجيل الدخول عبر Google
┌────────────────────────────────────────────────────────────────────┐
│ Flutter │ PHP Backend │
├────────────────────────────────────────────────────────────────────┤
│ │
│ loginWithGoogleCredential(driverID, email): │
│ │
│ GET /auth/captin/loginFromGoogle.php?id=driverID │
│ │
│ ← هذا الملف يستخدم connect.php (قديم، لا JWT check) │
│ ← لا يستخدم email في البحث (معلق) — يبحث بـ id فقط │
│ │
│ خطوات الباك إند: │
│ 1. filterRequest('id') ← driverID │
│ 2. SELECT مع LEFT JOIN (phone_verification, CarRegistration, │
│ driver_gifts, invites) WHERE driver.id = :id │
│ 3. فك تشفير الحقول الحساسة │
│ 4. إرجاع { status, data: { phone, email, first_name, ... } } │
│ │
│ في التطبيق: │
│ 1. يخزّن بيانات كاملة في box │
│ 2. يحصل على JWT عبر getJWT() │
│ 3. يتحقق من driver token ← إذا تغير fingerprint → OTP Dialog │
│ 4. يدخل إلى HomeCaptain │
│ │
└────────────────────────────────────────────────────────────────────┘
مسار الـ JWT (Two-Phase)
┌────────────────────────────────────────────────────────────────────┐
│ Phase 1: Registration Token │
│ loginFirstTimeDriver.php │
│ ← Rate Limiter (5/دقيقة) ✅ │
│ ← Audience check (allowedDriver1/2) ✅ │
│ ← password_verify(password, passwordnewpassenger) ✅ │
│ ← Fingerprint + Pepper → SHA-256 ✅ │
│ ← JWT: token_type='registration', exp=450s (7.5 min) │
│ ← Secret Key من ملف خارجي (.secret_key) ✅ │
│ │
│ Phase 2: Access Token │
│ loginJwtDriver.php │
│ ← Rate Limiter (5/دقيقة) ✅ │
│ ← Audience check ✅ │
│ ← قراءة driver من DB (SELECT id, phone, national_number, password)│
│ ← فك تشفير phone و national_number │
│ ← بناء HMAC: sha256(id|phone|national_number, SECRET_KEY_HMAC) │
│ ← password_verify(hmacHex, driver.password) ✅ │
│ ← توليد Access Token (JwtService.generateAccessToken) │
│ ← إلغاء التوكن القديم عبر Redis (Token Revocation) ✅ │
│ ← إرجاع { jwt, expires_in: 14400 (4h) } │
│ │
│ في التطبيق (getJWT): │
│ إذا firstTimeLoadKey != false → loginFirstTimeDriver │
│ وإلا → loginJwtDriver │
│ يحفظ JWT في box + secure storage │
└────────────────────────────────────────────────────────────────────┘
4. تحليل تدفق البيانات — من وإلى التطبيق
📤 من التطبيق إلى الباك إند
| المعلومة | هل التنظيف صحيح؟ | الطريقة |
|---|---|---|
| ✅ | filterRequest('email') ثم encryptData() قبل الاستعلام |
|
| phone | ✅ | filterRequest('phone') ثم encryptData() قبل الاستعلام |
| password | ✅ | يُستخدم password_verify() مع password_hash في DB |
| fingerprint | ✅ | يُهش مع Pepper: SHA-256(fingerprint + pepper) |
| driverID | ✅ | filterRequest('id') |
| token / otp | ⚠️ | token_code يُرسل بدون تشفير في /verifyOtpDriver |
📥 من الباك إند إلى التطبيق
| المعلومة | هل التسريب آمن؟ | ملاحظة |
|---|---|---|
| JWT | ✅ | يُخزَّن في GetStorage + FlutterSecureStorage |
| HMAC | ✅ | يُستخدم للتوثيق بين الطلبات |
| الاسم (first_name, last_name) | ✅ | يُفك تشفيره للعرض فقط |
| الهاتف والإيميل | ✅ | يُفك تشفيرهما للعرض فقط |
| make, model, year | ✅ | غير حساسة، لا تشفير |
| bankCode, accountBank | ⚠️ | حساسة لكنها تُفك تشفيرها وتُرسل |
5. النقاط الأمنية والثغرات
✅ نقاط القوة
- Rate Limiting في loginJwtDriver.php و loginFirstTimeDriver.php ✅
- JWT مع Fingerprint و Pepper — ربط التوكن بالجهاز ✅
- HMAC للمصادقة الداخلية — مشتق من id|phone|national_number ✅
- إلغاء التوكن القديم عبر Redis قبل إصدار جديد ✅
- تشفير PII في قاعدة البيانات — encryptData لكل الحقول الحساسة ✅
- password_hash + password_verify في كل نقاط التحقق ✅
- Audience Validation — التحقق من الـ audience المسموح ✅
- NetGuard في CRUD — التحقق من SSL قبل الاتصال ✅
- فصل التوكنات — registration token (450s) ≠ access token (4h) ✅
- فحص صلاحية JWT —
_isJwtValid()قبل أي طلب ✅
❌ الثغرات والملاحظات
| # | الثغرة | الموقع | التأثير | التصنيف |
|---|---|---|---|---|
| 1 | loginFromGoogle.php يقرأ connect.php (قديم، لا JWT) | backend/auth/captin/loginFromGoogle.php |
لا يوجد JWT Authentication — يمكن لأي جهة خارجية استدعاء API وسحب بيانات السائق | 🔴 Critical |
| 2 | loginUsingCredentialsWithoutGoogle.php يقرأ connect.php (قديم) | backend/auth/captin/loginUsingCredentialsWithoutGoogle.php |
لا يوجد JWT Authentication — يمكن اختراق كلمة المرور بقوة عمياء دون Rate Limiting | 🔴 Critical |
| 3 | login.php (auth/captin) لا يستخدم connect.php الجديد | backend/auth/captin/login.php |
لا Rate Limiting ولا JWT Authentication | 🔴 Critical |
| 4 | is_verified = '1' إجباري في loginUsingCredentials | loginUsingCredentialsWithoutGoogle.php سطر 38 |
السائق يحتاج التحقق من الهاتف حتى يتمكن من تسجيل الدخول (قد يكون مقصوداً لكنه قاسٍ على المستخدم) | 🟡 Medium |
| 5 | connect.php القديم | backend/connect.php نفسه |
لا Rate Limiting ولا JWT — أي API يستخدم connect.php فقط مكشوف للجميع | 🔴 Critical |
| 6 | loginFromGoogle.php لا يتحقق من البريد (معلق) | سطر 10-18 | البحث فقط بـ driverID — أي driverID صحيح يعيد البيانات حتى لو الإيميل مختلف |
🟡 Medium |
| 7 | OTP code يرسل إلى الباك إند نصاً بدون تشفير | register_captin_controller.dart سطر 266 |
token_code يُرسل كـ plain text |
🟡 Medium |
| 8 | Secure Storage vs GetStorage | login_captin_controller.dart |
JWT يُخزَّن في كلاهما — GetStorage غير مشفر (plain text على القرص) | 🔴 Critical |
| 9 | password مخزَّن في متغير بيئة (passwordnewpassenger) | loginFirstTimeDriver.php سطر 30 |
كلمة مرور عامة لكل السائقين للتسجيل الأولي — إذا سُرّبت، يمكن توليد registration tokens وهمية | 🔴 Critical |
| 10 | لا Validation على الـ id في loginJwtDriver | loginJwtDriver.php |
إذا تم إرسال id غير موجود، يعيد "invalid credentials" (وهو صحيح لكن يمكن استغلاله في تحديد IDs) | 🟢 Low |
6. مخطط الـ OTP — كامل
┌───────────────────┐ ┌─────────────────────┐
│ Flutter │ │ PHP Backend │
├───────────────────┤ ├─────────────────────┤
│ │ │ │
│ sendOtpMessage() │ │ │
│ │ │ │ │
│ ├── checkPhoneNumberISVerfiedDriver │
│ │ POST ──────┼────────>│ التحقق إذا الرقم │
│ │ │ │ موثَّق مسبقاً │
│ │ ← is_verified=1 ──────┤ │
│ │ ← already verified → loginWithGoogleCredential│
│ │ │ │ │
│ ├── sendVerifyOtpMessage │ │
│ │ POST: {phone_number, driverId, email} │
│ │ ───────────┼────────>│ إرسال OTP عبر SMS │
│ │ │ │ + حفظ في DB │
│ │ │ │ │
│ ├── verifyOtpDriver (verifySMSCode) │
│ │ POST: {phone_number, token_code} │
│ │ ───────────┼────────>│ التحقق من OTP │
│ │ │ │ │
│ │ ← success ───────────│ │
│ │ phoneVerified=1 │ │
│ │ ├── تخزين phoneDriver │ │
│ │ └── Get.to(RegistrationView) │
│ │ │ │
└───────────────────┘ └─────────────────────┘
الثغرة في تدفق OTP:
عندما يكون is_verified=1، ينتقل التطبيق مباشرةً إلى loginWithGoogleCredential() دون إعادة إدخال كلمة المرور أو أي تحقق إضافي. هذا يعني أنه إذا تم الوصول إلى جهاز المستخدم مؤقتاً، يمكن تسجيل الدخول فقط بالتحقق من أن الرقم "موثَّق مسبقاً".
7. الملخص النهائي
| البند | النتيجة |
|---|---|
| إجمالي الملفات المدققة | 22 ملفاً (Flutter + PHP) |
| الثغرات الحرجة (Critical) | 5 |
| الثغرات المتوسطة (Medium) | 3 |
| الثغرات المنخفضة (Low) | 2 |
| النقاط الإيجابية | 10 |
الثغرات الحرجة (Critical) التي تتطلب إصلاحاً فورياً:
- 🔴 loginFromGoogle.php ← يستخدم connect.php القديم (لا JWT, لا Rate Limiting)
- 🔴 loginUsingCredentialsWithoutGoogle.php ← يستخدم connect.php القديم
- 🔴 auth/captin/login.php ← لا Rate Limiting ولا JWT
- 🔴 JWT مخزَّن في GetStorage (غير مشفر) — يجب استخدام FlutterSecureStorage فقط
- 🔴 passwordnewpassenger عام لكل السائقين في loginFirstTimeDriver.php
هل التدفق صحيح؟
- من التطبيق إلى الباك إند: ✅ صحيح —
filterRequest()ينظف المدخلات - التشفير في DB: ✅ صحيح —
encryptData()لكل الحقول الحساسة - التحقق من كلمة المرور: ✅ صحيح —
password_hash()+password_verify() - Fingerprint + Pepper: ✅ صحيح — يربط التوكن بالجهاز
- إدارة التوكنات: ✅ صحيح — Registration ثم Access مع Revocation
- المصادقة (Authentication): ❌ ضعيف — الملفات القديمة (connect.php) لا تتطلب JWT
- تخزين البيانات الحساسة: ❌ ضعيف — GetStorage غير مشفر على Android
التوصية:
يجب تحويل loginFromGoogle.php و loginUsingCredentialsWithoutGoogle.php لاستخدام core/bootstrap.php مع JWT Authentication بدلاً من connect.php القديم، وتوحيد GetStorage إلى FlutterSecureStorage للـ JWT.
تاريخ التحليل: 12 يونيو 2026
النسخة: 1.0
التركيز: Siro Driver فقط