323 lines
23 KiB
Markdown
323 lines
23 KiB
Markdown
<div dir="rtl" lang="ar">
|
|
|
|
# تحليل تدفق تسجيل ودخول السائق (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. تحليل تدفق البيانات — من وإلى التطبيق
|
|
|
|
### 📤 من التطبيق إلى الباك إند
|
|
|
|
| المعلومة | هل التنظيف صحيح؟ | الطريقة |
|
|
|----------|-------------------|---------|
|
|
| email | ✅ | `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. النقاط الأمنية والثغرات
|
|
|
|
### ✅ نقاط القوة
|
|
|
|
1. **Rate Limiting** في loginJwtDriver.php و loginFirstTimeDriver.php ✅
|
|
2. **JWT مع Fingerprint و Pepper** — ربط التوكن بالجهاز ✅
|
|
3. **HMAC للمصادقة الداخلية** — مشتق من id\|phone\|national_number ✅
|
|
4. **إلغاء التوكن القديم** عبر Redis قبل إصدار جديد ✅
|
|
5. **تشفير PII في قاعدة البيانات** — encryptData لكل الحقول الحساسة ✅
|
|
6. **password_hash + password_verify** في كل نقاط التحقق ✅
|
|
7. **Audience Validation** — التحقق من الـ audience المسموح ✅
|
|
8. **NetGuard في CRUD** — التحقق من SSL قبل الاتصال ✅
|
|
9. **فصل التوكنات** — registration token (450s) ≠ access token (4h) ✅
|
|
10. **فحص صلاحية 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) التي تتطلب إصلاحاً فورياً:
|
|
|
|
1. **🔴 loginFromGoogle.php** ← يستخدم connect.php القديم (لا JWT, لا Rate Limiting)
|
|
2. **🔴 loginUsingCredentialsWithoutGoogle.php** ← يستخدم connect.php القديم
|
|
3. **🔴 auth/captin/login.php** ← لا Rate Limiting ولا JWT
|
|
4. **🔴 JWT مخزَّن في GetStorage (غير مشفر)** — يجب استخدام FlutterSecureStorage فقط
|
|
5. **🔴 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 فقط
|
|
|
|
</div> |