Files
Siro/driver_auth_flow_analysis.md
2026-06-12 20:40:40 +03:00

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>