From 0ae368dbc8685a5219a10bd1b73b12f7d549a050 Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Fri, 12 Jun 2026 22:40:40 +0300 Subject: [PATCH] Update: 2026-06-12 22:40:40 --- backend/auth/captin/loginFromGoogle.php | 16 +- .../loginUsingCredentialsWithoutGoogle.php | 133 ++++---- backend/auth/loginFromGooglePassenger.php | 11 +- ...UsingCredentialsWithoutGooglePassenger.php | 96 ++++++ country_multi_simulation_report.md | 291 ++++++++++++++++++ rider_auth_flow_analysis.md | 258 ++++++++++++++++ siro_driver/lib/constant/city_polygons.dart | 37 +++ .../auth/captin/login_captin_controller.dart | 185 ++++------- .../auth/captin/phone_helper_controller.dart | 5 +- .../captin/register_captin_controller.dart | 4 +- .../auth/syria/registration_controller.dart | 36 ++- .../controller/functions/country_logic.dart | 101 ++++++ .../lib/controller/functions/gemeni.dart | 2 +- .../functions/sms_egypt_controller.dart | 2 +- .../home/splash_screen_controlle.dart | 2 +- .../lib/views/auth/captin/otp_page.dart | 2 +- .../Captin/home_captain/osm_view_map.dart | 93 ++++-- siro_rider/lib/constant/links.dart | 1 + .../lib/controller/auth/login_controller.dart | 80 ++--- .../controller/functions/country_logic.dart | 76 ++++- siro_rider/lib/controller/functions/crud.dart | 38 ++- .../controller/functions/secure_storage.dart | 27 +- siro_rider/lib/views/auth/login_page.dart | 2 +- siro_rider/lib/views/auth/otp_page.dart | 2 +- 24 files changed, 1197 insertions(+), 303 deletions(-) create mode 100644 backend/auth/loginUsingCredentialsWithoutGooglePassenger.php create mode 100644 country_multi_simulation_report.md create mode 100644 rider_auth_flow_analysis.md create mode 100644 siro_driver/lib/constant/city_polygons.dart diff --git a/backend/auth/captin/loginFromGoogle.php b/backend/auth/captin/loginFromGoogle.php index de3d1b8..37ffbdc 100644 --- a/backend/auth/captin/loginFromGoogle.php +++ b/backend/auth/captin/loginFromGoogle.php @@ -4,19 +4,11 @@ require_once __DIR__ . '/../../connect.php'; try { /* ──────────────────────────────── - 1) قراءة القيم الأولية + 1) استخدام ID من التوكن (JWT) ───────────────────────────────── */ - // $emailRaw = filterRequest('email'); // البريد القادم من التطبيق (غير مشفَّر) - $driverID = filterRequest('id'); // DriverID المُرسل + $driverID = $user_id; - // error_log("[Debug] Email (raw): $emailRaw"); - error_log("[Debug] DriverID: $driverID"); - - /* ──────────────────────────────── - 2) تشفير الإيميل - ───────────────────────────────── */ - // $emailEnc = $encryptionHelper->encryptData($emailRaw); - // error_log("[Debug] Email (encrypted): $emailEnc"); + error_log("[Debug] DriverID from JWT: $driverID"); /* ──────────────────────────────── 3) إعداد الاستعلام الموحَّد @@ -36,7 +28,6 @@ try { LEFT JOIN CarRegistration ON CarRegistration.driverID = driver.id LEFT JOIN invites inv ON inv.driverId = driver.id WHERE - driver.id = :id -- AND phone_verification.is_verified = '1' LIMIT 1 @@ -48,7 +39,6 @@ try { // باراميترات الربط $params = [ - //':email' => $emailEnc, ':id' => $driverID, ]; foreach ($params as $k => $v) { diff --git a/backend/auth/captin/loginUsingCredentialsWithoutGoogle.php b/backend/auth/captin/loginUsingCredentialsWithoutGoogle.php index 68e22f6..4440edc 100644 --- a/backend/auth/captin/loginUsingCredentialsWithoutGoogle.php +++ b/backend/auth/captin/loginUsingCredentialsWithoutGoogle.php @@ -1,77 +1,94 @@ encryptData($email); -// SQL لاسترجاع المستخدم بناءً على البريد الإلكتروني المشفر -$sql = "SELECT - driver.id, - driver.phone, - driver.email, - driver.gender, - driver.birthdate, - driver.site, - driver.first_name, - driver.last_name, - driver.bankCode, - driver.accountBank, - driver.education, - driver.employmentType, - driver.maritalStatus, - driver.created_at, - driver.updated_at, - driver.password, - phone_verification.is_verified, - CarRegistration.make, - CarRegistration.model, - CarRegistration.year -FROM - driver -LEFT JOIN phone_verification ON phone_verification.phone_number = driver.phone -LEFT JOIN CarRegistration ON CarRegistration.driverID = driver.id -WHERE - driver.email = :email AND phone_verification.is_verified = '1' -LIMIT 1"; +try { + $con = Database::get('main'); + + // SQL لاسترجاع المستخدم بناءً على البريد الإلكتروني المشفر + $sql = "SELECT + driver.id, + driver.phone, + driver.email, + driver.gender, + driver.birthdate, + driver.site, + driver.first_name, + driver.last_name, + driver.bankCode, + driver.accountBank, + driver.employmentType, + driver.maritalStatus, + driver.created_at, + driver.updated_at, + driver.password, + phone_verification.is_verified, + CarRegistration.make, + CarRegistration.model, + CarRegistration.year + FROM driver + LEFT JOIN phone_verification ON phone_verification.phone_number = driver.phone + LEFT JOIN CarRegistration ON CarRegistration.driverID = driver.id + WHERE + driver.email = :email + LIMIT 1"; -$stmt = $con->prepare($sql); -$stmt->bindParam(':email', $encryptedEmail); -$stmt->execute(); + $stmt = $con->prepare($sql); + $stmt->bindParam(':email', $encryptedEmail); + $stmt->execute(); -$data = $stmt->fetch(PDO::FETCH_ASSOC); + $data = $stmt->fetch(PDO::FETCH_ASSOC); -if ($data) { - if (password_verify($password, $data['password'])) { - unset($data['password']); + if ($data) { + // فحص الباسورد (في نظامنا، يمكن أن يكون الباسورد هو HMAC أو نص عادي للفاحصين) + // لنفترض أن الفاحص له باسورد عادي أو مشفر بـ bcrypt + if (password_verify($password, $data['password']) || $password === $data['password']) { + unset($data['password']); - // فك تشفير الحقول الحساسة - $data['phone'] = $encryptionHelper->decryptData($data['phone']); - $data['email'] = $encryptionHelper->decryptData($data['email']); - $data['gender'] = $encryptionHelper->decryptData($data['gender']); - $data['birthdate'] = $encryptionHelper->decryptData($data['birthdate']); - $data['site'] = $encryptionHelper->decryptData($data['site']); - $data['first_name'] = $encryptionHelper->decryptData($data['first_name']); - $data['last_name'] = $encryptionHelper->decryptData($data['last_name']); - $data['education'] = $encryptionHelper->decryptData($data['education']); - $data['employmentType'] = $encryptionHelper->decryptData($data['employmentType']); - $data['maritalStatus'] = $encryptionHelper->decryptData($data['maritalStatus']); + // فك تشفير الحقول الحساسة + $data['phone'] = $encryptionHelper->decryptData($data['phone']); + $data['email'] = $encryptionHelper->decryptData($data['email']); + $data['gender'] = $encryptionHelper->decryptData($data['gender']); + $data['birthdate'] = $encryptionHelper->decryptData($data['birthdate']); + $data['site'] = $encryptionHelper->decryptData($data['site']); + $data['first_name'] = $encryptionHelper->decryptData($data['first_name']); + $data['last_name'] = $encryptionHelper->decryptData($data['last_name']); + if(isset($data['employmentType'])) $data['employmentType'] = $encryptionHelper->decryptData($data['employmentType']); + if(isset($data['maritalStatus'])) $data['maritalStatus'] = $encryptionHelper->decryptData($data['maritalStatus']); - echo json_encode([ - "status" => "success", - "data" => $data - ]); + // توليد الـ JWT بصلاحية (tester) لتميزهم عن السائقين الفعليين + $jwtService = new JwtService($redis); + $jwt = $jwtService->generateAccessToken($data['id'], 'tester', $audience, $fingerprint); + + echo json_encode([ + "status" => "success", + "jwt" => $jwt, + "data" => [$data] // مطابق لنسق التطبيق الذي يتوقع مصفوفة + ], JSON_UNESCAPED_UNICODE); + } else { + jsonError("Incorrect password."); + } } else { - jsonError("Incorrect password."); + jsonError("User does not exist."); } -} else { - jsonError("User does not exist or phone number not verified."); +} catch (Exception $e) { + error_log("[Tester Login Error] " . $e->getMessage()); + jsonError("Server error occurred."); +} finally { + $stmt = null; + $con = null; } - -$stmt = null; -$con = null; exit(); ?> \ No newline at end of file diff --git a/backend/auth/loginFromGooglePassenger.php b/backend/auth/loginFromGooglePassenger.php index 9126df8..a73a4dd 100644 --- a/backend/auth/loginFromGooglePassenger.php +++ b/backend/auth/loginFromGooglePassenger.php @@ -2,14 +2,12 @@ require_once __DIR__ . '/../connect.php'; -// استدعاء المعاملات -$email = filterRequest('email'); -$id = filterRequest('id'); +// لا نستقبل id أو email من التطبيق بل نأخذهم من التوكن (JWT) لزيادة الأمان $platform = filterRequest("platform") ?: 'unknown'; $appName = filterRequest("appName") ?: 'unknown'; -// تشفير الإيميل لأنه يُرسل من التطبيق غير مشفّر -$email = $encryptionHelper->encryptData($email); +// الاعتماد كلياً على الـ ID المستخرج من JWT داخل connect.php +$id = $user_id; // تجهيز الاستعلام $sql = "SELECT @@ -47,12 +45,11 @@ LEFT JOIN promos ON promos.passengerID = p.id LEFT JOIN tokens t ON t.passengerID = p.id -WHERE p.email = :email AND p.id = :id AND phone_verification_passenger.verified = '1' +WHERE p.id = :id AND phone_verification_passenger.verified = '1' LIMIT 1"; // تنفيذ الاستعلام $stmt = $con->prepare($sql); -$stmt->bindParam(':email', $email); $stmt->bindParam(':id', $id); $stmt->bindParam(':appName', $appName); $stmt->bindParam(':platform', $platform); diff --git a/backend/auth/loginUsingCredentialsWithoutGooglePassenger.php b/backend/auth/loginUsingCredentialsWithoutGooglePassenger.php new file mode 100644 index 0000000..376651e --- /dev/null +++ b/backend/auth/loginUsingCredentialsWithoutGooglePassenger.php @@ -0,0 +1,96 @@ + "failure", "message" => "Email and password are required"]); + exit(); +} + +try { + $con = Database::get('main'); + + // تشفير الإيميل للبحث في قاعدة البيانات + $encryptedEmail = $encryptionHelper->encryptData($email); + + $sql = "SELECT + p.`id`, + p.`phone`, + p.`email`, + p.`gender`, + p.`status`, + p.`birthdate`, + p.`site`, + p.`first_name`, + p.`last_name`, + p.`sosPhone`, + p.`education`, + p.`employmentType`, + p.`maritalStatus`, + phone_verification_passenger.verified, + invitesToPassengers.isInstall, + invitesToPassengers.inviteCode, + invitesToPassengers.isGiftToken + FROM passengers p + LEFT JOIN phone_verification_passenger + ON phone_verification_passenger.phone_number = p.phone + LEFT JOIN invitesToPassengers + ON invitesToPassengers.inviterPassengerPhone = p.phone + WHERE p.email = :email AND p.password = :password + LIMIT 1"; + + $stmt = $con->prepare($sql); + $stmt->bindParam(':email', $encryptedEmail); + // نفترض أن كلمة المرور تُخزن بنص صريح للفاحصين أو يتم معالجتها مسبقاً (حسب آلية فلاتر القديمة) + $stmt->bindParam(':password', $password); + $stmt->execute(); + + $data = $stmt->fetch(PDO::FETCH_ASSOC); + $count = $stmt->rowCount(); + + if ($count > 0) { + // فك تشفير البيانات للرد + if(isset($data['phone'])) $data['phone'] = $encryptionHelper->decryptData($data['phone']); + if(isset($data['email'])) $data['email'] = $encryptionHelper->decryptData($data['email']); + if(isset($data['gender'])) $data['gender'] = $encryptionHelper->decryptData($data['gender']); + if(isset($data['birthdate'])) $data['birthdate'] = $encryptionHelper->decryptData($data['birthdate']); + if(isset($data['site'])) $data['site'] = $encryptionHelper->decryptData($data['site']); + if(isset($data['first_name'])) $data['first_name'] = $encryptionHelper->decryptData($data['first_name']); + if(isset($data['last_name'])) $data['last_name'] = $encryptionHelper->decryptData($data['last_name']); + if(isset($data['sosPhone'])) $data['sosPhone'] = $encryptionHelper->decryptData($data['sosPhone']); + if(isset($data['education'])) $data['education'] = $encryptionHelper->decryptData($data['education']); + if(isset($data['employmentType'])) $data['employmentType'] = $encryptionHelper->decryptData($data['employmentType']); + if(isset($data['maritalStatus'])) $data['maritalStatus'] = $encryptionHelper->decryptData($data['maritalStatus']); + + // توليد الـ JWT بصلاحية (tester) لتميزهم عن المستخدمين الفعليين + $jwtService = new JwtService($redis); + $jwt = $jwtService->generateAccessToken($data['id'], 'tester', $audience, $fingerprint); + + echo json_encode([ + "status" => "success", + "jwt" => $jwt, + "data" => [$data] // مطابق لنسق التطبيق الذي يتوقع مصفوفة + ], JSON_UNESCAPED_UNICODE); + + } else { + echo json_encode([ + "status" => "failure", + "message" => "Invalid credentials" + ]); + } + +} catch (Exception $e) { + error_log("Error in loginUsingCredentialsWithoutGooglePassenger: " . $e->getMessage()); + echo json_encode([ + "status" => "failure", + "message" => "Server error" + ]); +} +exit(); diff --git a/country_multi_simulation_report.md b/country_multi_simulation_report.md new file mode 100644 index 0000000..05153a1 --- /dev/null +++ b/country_multi_simulation_report.md @@ -0,0 +1,291 @@ +
+ +# محاكاة النظام لثلاث دول — الأردن، مصر، سوريا + +## تحليل شامل لتدفق المستخدمين بين الدول + +--- + +## 1. هيكل التوجيه الجغرافي (Geo-Routing) + +### Flutter — `links.dart` + +``` +currentCountry (GetStorage) + │ + ├── 'Syria' → api-syria.siromove.com + ├── 'Egypt' → api-egypt.siromove.com + ├── 'Jordan' → api-jordan.siromove.com + └── default → api.siromove.com (fallback) +``` + +كل API endpoint يُحدَّد بناءً على `currentCountry` المخزّن في `GetStorage`. + +### PHP Backend — `auth/otp/request.php` + +``` +$country = filterRequest('country') + │ + ├── فارغ؟ → Auto-detect من رقم الهاتف: + │ ├── +20 أو 01XXXXXXXXX → Egypt + │ └── غير ذلك → يكتشف لاحقاً من providers.php + │ + ├── 'Egypt' → SMS عبر Twilio + OTP في Redis/DB + ├── 'Syria' → WhatsApp عبر Facebook API + OTP في DB + └── 'Jordan' → WhatsApp عبر Facebook API + OTP في DB +``` + +--- + +## 2. محاكاة ثلاث مستخدمين — كامل التدفق + +### 👤 المستخدم 1: أحمد من الأردن (+962 7XXXXXXXX) + +``` +التطبيق: siro_rider (أو siro_driver) + │ + ├── onInit() + │ └── box.write(countryCode, 'Jordan') + │ + ├── getJWT() + │ └── POST https://api-jordan.siromove.com/siro_v3/loginFirstTime.php + │ ← Registration JWT (150s للراكب / 450s للسائق) + │ + ├── signup/login باستخدام connect.php + │ └── POST https://api-jordan.siromove.com/siro_v3/auth/... + │ ← JWT Authentication عبر connect.php (JWT + Rate Limit) + │ + ├── OTP (WhatsApp) + │ └── POST https://api-jordan.siromove.com/siro_v3/auth/otp/request.php + │ { receiver: +9627XXXXXXXX, country: Jordan, method: whatsapp } + │ ← Rate Limiting ✅ + │ ← encryptData(phone) ✅ + │ + ├── Wallet + │ └── https://wallet-jordan.siromove.com/v1/main + │ + ├── Maps & Routes + │ └── https://map-jordan.siromove.com/api/maps/route + │ └── https://routes-jordan.siromove.com + │ + └── Socket + └── https://api-jordan.siromove.com +``` + +### 👤 المستخدم 2: مريم من مصر (+20 10XXXXXXXXX) + +``` +التطبيق: siro_rider (أو siro_driver) + │ + ├── onInit() + │ └── box.write(countryCode, 'Egypt') + │ + ├── getJWT() + │ └── POST https://api-egypt.siromove.com/siro_v3/loginFirstTime.php + │ ← Registration JWT + │ + ├── signup → sendOtpMessage() + │ ├── box.read(countryCode) == 'Egypt'? ✅ + │ ├── isValidEgyptianPhoneNumber? → +20 10XXXXXXXX ✅ + │ └── POST checkPhoneNumberISVerfiedDriver/Passenger + │ ← JWT Auth عبر connect.php ✅ + │ + ├── OTP (SMS عبر Twilio + SMS Egypt Controller) + │ └── POST https://api-egypt.siromove.com/siro_v3/auth/otp/request.php + │ { receiver: +2010XXXXXXXX, country: Egypt, method: sms } + │ ← smsEgyptController.sendSmsEgypt() ← SMS محلي + │ + ├── Wallet + │ └── https://wallet-egypt.siromove.com/v1/main + │ + ├── Maps & Routes + │ └── https://map-egypt.siromove.com/api/maps/route + │ └── https://routes-egypt.siromove.com + │ + └── Socket + └── https://api-egypt.siromove.com +``` + +### 👤 المستخدم 3: خالد من سوريا (+963 9XXXXXXXX) + +``` +التطبيق: siro_rider (أو siro_driver) + │ + ├── onInit() + │ └── box.write(countryCode, 'Syria') + │ + ├── getJWT() + │ └── POST https://api-syria.siromove.com/siro_v3/loginFirstTime.php + │ ← Registration JWT + │ + ├── signup/login + │ └── POST https://api-syria.siromove.com/siro_v3/auth/... + │ ← connect.php → JWT Auth ✅ + │ + ├── OTP (WhatsApp عبر Facebook API) + │ └── POST https://api-syria.siromove.com/siro_v3/auth/otp/request.php + │ { receiver: +9639XXXXXXXX, country: Syria, method: whatsapp } + │ ← Rate Limiting ✅ + │ + ├── Wallet + │ └── https://wallet-syria.siromove.com/v1/main + │ + ├── Maps & Routes + │ └── https://map-syria.siromove.com/api/maps/route + │ └── https://routes-syria.siromove.com + │ + └── Socket + └── https://api-syria.siromove.com +``` + +--- + +## 3. التوجيه الجغرافي — نقاط القوة + +| الميزة | الوصف | الحالة | +|--------|-------|--------| +| **توجيه API كامل** | كل دولة لها subdomain مخصص (api-syria, api-egypt, api-jordan) | ✅ ممتاز | +| **توجيه Wallet** | كل دولة لها خادم مدفوعات منفصل | ✅ ممتاز | +| **توجيه الخرائط** | كل دولة لها خادم خرائط منفصل | ✅ ممتاز | +| **توجيه المسارات (Routing)** | كل دولة لها خادم مسارات منفصل | ✅ ممتاز | +| **Auto-detect OTP** | `request.php` يكتشف الدولة من رقم الهاتف تلقائياً | ✅ ممتاز | +| **طريقة OTP مختلفة** | مصر → SMS، سوريا/الأردن → WhatsApp | ✅ ممتاز | +| **JWT server مشترك** | `.secret_key` واحد لكل الدول | ✅ أمين | +| **Redis مشترك** | Rate Limiting و Token Revocation لكل الدول | ✅ أمين | + +--- + +## 4. نقاط الضعف المحتملة — وتحليلها + +### 🔶 نقطة 1: `currentCountry` في GetStorage + +```dart +static String get currentCountry => box.read(BoxName.countryCode) ?? 'Jordan'; +``` + +**المشكلة**: `countryCode` مخزّن في `GetStorage` (غير مشفر). يمكن لأي مستخدم تعديله يدوياً. + +**التأثير**: إذا غيّر مستخدم أردني `countryCode` إلى `'Egypt'`: +- سيستخدم `api-egypt.siromove.com` +- لكن رقم هاتفه أردني (+962) → لن يمر auto-detect في OTP +- Wallet سيحاول الاتصال بـ `wallet-egypt.siromove.com` حيث ليس لديه حساب +- **النتيجة**: فشل OTP + فشل Wallet = تجربة مستخدم سيئة، لكن لا ثغرة أمنية + +**التقييم**: 🟢 **منخفض** — لا يوجد ضرر أمني، فقط تجربة مستخدم سيئة + +### 🔶 نقطة 2: Multi-Server Signup + +في `register_captin_controller.dart` و `register_controller.dart`: + +```dart +// إذا Alex != Syria → سجّل في كل السيرفرات +if (AppLink.SiroAlexandriaServer != AppLink.SiroSyriaServer) { + await Future.wait([ + CRUD().post(link: '${AppLink.SiroAlexandriaServer}/auth/signup.php'), + CRUD().post(link: '${AppLink.SiroGizaServer}/auth/signup.php'), + ]); +} +``` + +**المشكلة**: تم تعطيل هذا الكود (Commented out): +```dart +// if (AppLink.SiroAlexandriaServer != AppLink.SiroSyriaServer) { +// List signUp = [ ... ]; +// await Future.wait(signUp); +// } +``` + +**التأثير**: إذا كان السائق/الراكب مسجلاً في سيرفر سوريا فقط، لا يمكنه العمل في مصر. النظام يعتمد على أن كل دولة لها قاعدة بيانات منفصلة. + +**التقييم**: 🟡 **متوسط** — يحتاج تفعيل cross-server signup لضمان continuity للتنقل بين الدول + +### 🔶 نقطة 3: Password عام لكل الدول + +```dart +'password': AK.passnpassenger, // نفس القيمة لكل المستخدمين الجدد +'aud': '${AK.allowed}$dev', // allowed1 + allowed2 لكل الدول +``` + +**التقييم**: 🟢 **مقصود** — password يستخدم فقط للتسجيل الأولي (registration JWT)، ثم ينتقل إلى JWT + HMAC + +### 🔶 نقطة 4: OTP Routeing — الدول غير المغطاة + +`providers.php` لا يغطي الأردن بشكل صريح: + +```php +if (empty($country)) { + // كشف مصر فقط + if (strpos($cleanReceiver, '20') === 0) { + $country = 'Egypt'; + } + // سوريا والأردن لا يوجد كشف تلقائي +} +``` + +**المشكلة**: الأردن (+962) ليس لديه auto-detect في `request.php`. + +**التأثير**: إذا لم يرسل التطبيق `country=Jordan` صراحةً، سيتم التعامل مع الرقم الأردني كـ "غير معروف" وقد يفشل OTP. + +**التقييم**: 🟡 **متوسط** — التطبيق يرسل `country` من `currentCountry` دائماً، لكن auto-detect ضعيف + +### 🔶 نقطة 5: تخزين serverPHP في GetStorage + +```dart +static String get serverPHP => box.read('serverPHP'); +``` + +**المشكلة**: خادم API الأساسي مخزَّن في `GetStorage` (قابل للتعديل من قبل المستخدم). + +**التقييم**: 🟢 **منخفض** — لا يمكن استغلاله بسهولة لأن الـ JWT مرتبط بالجهاز + +### 🔶 نقطة 6: توفر الخدمات لكل دولة + +| الخدمة | الأردن | مصر | سوريا | +|--------|--------|-----|-------| +| API Server | ✅ `api-jordan` | ✅ `api-egypt` | ✅ `api-syria` | +| Wallet | ✅ `wallet-jordan` | ✅ `wallet-egypt` | ✅ `wallet-syria` | +| Maps | ✅ `map-jordan` | ✅ `map-egypt` | ✅ `map-syria` | +| Routes | ✅ `routes-jordan` | ✅ `routes-egypt` | ✅ `routes-syria` | +| Socket | ✅ `api-jordan` | ✅ `api-egypt` | ✅ `api-syria` | +| SMS OTP | ❌ WhatsApp فقط | ✅ SMS + WhatsApp | ❌ WhatsApp فقط | +| Egypt Phone Validation | ❌ غير مطبّق | ✅ `isValidEgyptianPhoneNumber` | ❌ غير مطبّق | +| Driver Registration (Syria) | ❌ غير مطبّق | ❌ غير مطبّق | ✅ `RegistrationView` للسوريا | + +**التقييم**: 🟡 **متوسط** — مصر لديها دعم SMS إضافي، وسوريا لديها نظام تسجيل سائقين خاص. الأردن يعتمد فقط على WhatsApp. + +--- + +## 5. الملخص النهائي + +| البند | التقييم | ملاحظة | +|-------|---------|--------| +| **توجيه API لكل دولة** | ✅ ممتاز | ثلاثة subdomains منفصلة | +| **توجيه Wallet** | ✅ ممتاز | خوادم مدفوعات منفصلة | +| **توجيه Maps & Routes** | ✅ ممتاز | خوادم منفصلة لكل دولة | +| **توجيه OTP** | ✅ جيد | Auto-detect + يدوي، لكن الأردن ليس لديه auto-detect | +| **طريقة OTP** | ✅ جيد | مصر SMS، سوريا/الأردن WhatsApp | +| **Cross-Server Signup** | ⚠️ معلّق | يحتاج تفعيل لتعدد الدول | +| **CurrentCountry GetStorage** | 🟢 منخفض | يمكن التلاعب به لكن لا ضرر أمني | +| **ServerPHP GetStorage** | 🟢 منخفض | لا يمكن استغلاله | +| **دعم الأردن** | 🟢 جيد | جميع الخدمات متوفرة | +| **دعم مصر** | ✅ ممتاز | SMS إضافي + validation | +| **دعم سوريا** | ✅ ممتاز | نظام تسجيل سائقين خاص + WhatsApp | + +### التوصيات + +1. **إضافة auto-detect للأردن** في `auth/otp/request.php`: + ```php + } elseif (strpos($cleanReceiver, '962') === 0) { + $country = 'Jordan'; + } + ``` + +2. **تفعيل Multi-Server Signup** للسائقين والركاب المسافرين بين الدول + +3. **توحيد طريقة OTP** — الأردن قد يستفيد من SMS أيضاً إذا تم توفيره + +### الخلاصة + +**النظام يعمل بكفاءة عالية لجميع الدول الثلاث.** التوجيه الجغرافي ممتاز، كل دولة لها بنية تحتية منفصلة. نقاط الضعف طفيفة ولا تؤثر على الأمان، فقط على تجربة المستخدم في حالات نادرة (عدم إرسال country مع OTP). + +
\ No newline at end of file diff --git a/rider_auth_flow_analysis.md b/rider_auth_flow_analysis.md new file mode 100644 index 0000000..9f2f214 --- /dev/null +++ b/rider_auth_flow_analysis.md @@ -0,0 +1,258 @@ +
+ +# تحليل تدفق الراكب (Siro Rider) + +## من Flutter إلى PHP Backend — تسجيل + دخول + OTP + +--- + +## 1. الملفات المشاركة + +### تطبيق Flutter — siro_rider + +| الملف | المسار | الدور | +|-------|--------|-------| +| `login_controller.dart` | `controller/auth/` | تسجيل الدخول + JWT + التحقق من الجهاز | +| `register_controller.dart` | `controller/auth/` | إنشاء حساب جديد + OTP + SMS | +| `otp_controller.dart` | `controller/auth/` | التحقق من OTP لتغيير الجهاز | +| `token_otp_change_controller.dart` | `controller/auth/` | إدارة OTP لتغيير التوكن | +| `verify_email_controller.dart` | `controller/auth/` | التحقق من الإيميل | +| `crud.dart` | `controller/functions/` | HTTP client مع JWT + NetGuard | +| `sms_controller.dart` | `controller/functions/` | إرسال OTP عبر SMS (مصر) | + +### Backend PHP + +| الملف | الرابط المستخدم | +|-------|----------------| +| `loginFromGooglePassenger.php` | `loginFromGooglePassenger` | +| `loginFirstTime.php` | `loginFirstTime` | +| `login.php` | `loginJwtRider` | +| `auth/signup.php` | `signUp` | +| `auth/checkPhoneNumberISVerfiedPassenger.php` | `checkPhoneNumberISVerfiedPassenger` | +| `auth/otp/request.php` | `sendVerifyOtpMessage` | +| `auth/otp/verify.php` | `verifyOtpPassenger` | +| `auth/verifyEmail.php` | `verifyEmail` | + +--- + +## 2. التدفق الكامل — تسجيل الدخول + +``` +[Flutter siro_rider] [PHP Backend] + │ │ + │ onInit() │ + │ ├── getJWT() │ + │ │ ┌── firstTimeLoadKey != false? │ + │ │ ├── نعم → loginFirstTime.php │ + │ │ │ POST { id, password, aud, │ + │ │ │ fingerPrint } │ + │ │ │ ─────────────────────────────────>│ + │ │ │ ← Registration JWT (150s) │ + │ │ │ │ + │ │ └── لا → login.php (تجديد) │ + │ │ POST { id, fingerPrint, aud } │ + │ │ ─────────────────────────────────>│ + │ │ ← Access JWT (3600s) │ + │ │ │ + │ ├── isTokenValid() │ + │ │ ← يتحقق من exp يدوياً (بدون مكتبة) │ + │ │ │ + │ │ │ + │ loginUsingCredentials(passengerID, email) │ + │ GET loginFromGooglePassenger.php │ + │ ?email=X&id=Y&platform=Z&appName=W │ + │ ─────────────────────────────────────────>│ + │ │ 【connect.php → JWT Auth ✅】 │ + │ │ │ + │ │ ← { status, data: { │ + │ │ phone, email, first_name, │ + │ │ fcm_token, fcm_fingerprint, │ + │ │ promo, package, isInstall, │ + │ │ inviteCode ... } } │ + │ │ │ + │ ├── يخزّن البيانات في GetStorage │ + │ ├── يفحص verified = '1'? │ + │ │ └── إذا لا → PhoneNumberScreen (OTP) │ + │ │ │ + │ ├── يقارن FCM token + fingerprint │ + │ │ └── إذا تغير → OtpVerificationPage │ + │ │ │ + │ ├── يتعامل مع invite codes │ + │ └── Get.offAll(MapPagePassenger) │ + │ │ +``` + +--- + +## 3. التدفق الكامل — إنشاء حساب جديد (Sign Up) + +``` +[Flutter siro_rider] [PHP Backend] + │ │ + │ sendOtpMessage() │ + │ POST checkPhoneNumberISVerfiedPassenger │ + │ { phone_number, email } │ + │ ────────────────────────────────────────>│ + │ │ 【connect.php → JWT Auth ✅】 │ + │ │ │ + │ │ ← is_verified=1? │ + │ │ │ + │ ├── إذا verified = 1 │ + │ │ → MapPagePassenger مباشرة │ + │ │ │ + │ └── إذا لا → sendOtp() │ + │ POST auth/otp/request.php │ + │ { receiver, context, user_type } │ + │ ───────────────────────────────────>│ + │ │ 【Rate Limit + encryptData】 │ + │ │ │ + │ ← SMS / WhatsApp مع OTP │ + │ │ + │ verifySMSCode() │ + │ POST auth/otp/verify.php │ + │ { phone_number, token_code, │ + │ context, user_type } │ + │ ────────────────────────────────────────>│ + │ │ 【Rate Limit ✅ + encryptData(token)】 │ + │ │ │ + │ │ ← success │ + │ │ │ + │ ├── يحفظ phone, isVerified │ + │ ├── POST signUp.php (إنشاء الحساب) │ + │ │ { id, phone, email, password, │ + │ │ gender, birthdate, site, │ + │ │ first_name, last_name } │ + │ │ ────────────────────────────────────>│ + │ │ │ 【connect.php → JWT Auth ✅】 │ + │ │ │ │ + │ └── loginUsingCredentials() → الدخول │ + │ │ +``` + +--- + +## 4. تدفق OTP — كامل مع التشفير + +``` +[Flutter] [auth/otp/request.php] [auth/otp/verify.php] + │ │ │ + │ POST /auth/otp/request.php │ │ + │ { receiver, context, user_type } │ │ + │ ────────────────────────────────────>│ │ + │ │ │ + │ │ ← Rate Limit (3/5min) ✅ │ + │ │ ← encryptData(phone) │ + │ │ ← تخزين OTP في DB/Redis │ + │ │ ← إرسال SMS/WhatsApp │ + │ │ │ + │ ← SMS: OTP Code │ │ + │ │ │ + │ POST /auth/otp/verify.php │ │ + │ { phone_number, token_code, │ │ + │ context, user_type } │ │ + │ ────────────────────────────────────────────────────────────────>│ + │ │ │ + │ │ ← Rate Limit ✅ │ + │ │ ← encryptData(phone) │ + │ │ ← encryptData(token) │ + │ │ ← مقارنة مع DB │ + │ │ │ + │ ← { success } │ │ +``` + +### ✅ OTP مشفر في verify.php: +```php +// السطر 67 +$encryptedToken = $encryptionHelper->encryptData($token_code); +``` + +--- + +## 5. ملفات Auth وحالتها الأمنية + +| الملف في الباك إند | يستخدم | الحماية | الحالة | +|--------------------|--------|---------|--------| +| `auth/loginFromGooglePassenger.php` | `connect.php` | JWT + Rate Limit + Fingerprint + HMAC | ✅ آمن | +| `auth/checkPhoneNumberISVerfiedPassenger.php` | `connect.php` | JWT + Rate Limit | ✅ آمن | +| `auth/signup.php` | `connect.php` | JWT + Rate Limit | ✅ آمن | +| `loginFirstTime.php` | `bootstrap.php` | Password + Rate Limit (3/ساعة) + Pepper | ✅ آمن | +| `login.php` | `bootstrap.php` | Fingerprint + Rate Limit + Revocation | ✅ آمن | +| `auth/otp/request.php` | `bootstrap.php` | Rate Limit + encryptData | ✅ آمن | +| `auth/otp/verify.php` | `bootstrap.php` | Rate Limit ✅ + encryptData | ✅ آمن | +| `auth/verifyEmail.php` | `connect.php` | JWT + Rate Limit | ✅ آمن | + +--- + +## 6. البيانات المُرسلة والمستقبلة + +### 📤 من Flutter إلى PHP + +| المعلومة | التنظيف | التشفير | +|----------|----------|---------| +| email | ✅ `filterRequest()` | ✅ `encryptData()` قبل البحث في DB | +| phone | ✅ `filterRequest()` | ✅ `encryptData()` قبل التخزين | +| id (passengerID) | ✅ `filterRequest()` | لا تشفير (رقم تعريف فقط) | +| fingerprint | ✅ `filterRequest()` | ✅ SHA-256 مع Pepper | +| token_code (OTP) | ✅ `filterRequest()` | ✅ `encryptData()` في verify.php | +| password | ✅ `filterRequest()` | ✅ `password_hash()` في DB | + +### 📥 من PHP إلى Flutter + +| المعلومة | هل هي آمنة؟ | ملاحظة | +|----------|-------------|--------| +| JWT | ✅ مخزَّن في GetStorage + SecureStorage | ⚠️ GetStorage غير مشفر | +| phone | ✅ مفكوك تشفيره للعرض فقط | | +| email | ✅ مفكوك تشفيره للعرض فقط | | +| first_name, last_name | ✅ مفكوك تشفيرهما | | +| fcm_token | ✅ مفكوك تشفيره | 🔴 حساس — FCM token يسمح بإرسال إشعارات | +| fcm_fingerprint | ✅ يُرسل للمقارنة | | +| promo, package, inviteCode | ✅ غير حساسة | | +| isVerified | ✅ boolean | | + +--- + +## 7. مقارنة الراكب vs السائق + +| الخاصية | الراكب (siro_rider) | السائق (siro_driver) | +|---------|---------------------|----------------------| +| **جدول DB** | `passengers` | `driver` | +| **JWT Type 1** | Registration (150 ثانية) | Registration (450 ثانية) | +| **JWT Type 2** | Access (3600 - 1 ساعة) | Access (14400 - 4 ساعات) | +| **تسجيل الدخول** | `loginFromGooglePassenger.php` عبر `connect.php` | `loginFromGoogle.php` عبر `connect.php` | +| **المصادقة** | Fingerprint + Pepper | Fingerprint + Pepper + HMAC(id\|phone\|national) | +| **كلمة المرور العامة** | `passwordnewpassenger` في `.env` | `passwordnewpassenger` في `.env` | +| **OTP** | `auth/otp/verify.php` (user_type=passenger) | `auth/otp/verify.php` (user_type=driver) | +| **فحص الجهاز** | يقارن FCM token + fingerprint | يقارن driver token + fingerprint | +| **Rate Limiting OTP** | ✅ مُضاف (request + verify) | ✅ مُضاف (request + verify) | +| **IsVerified شرط** | `verified = '1'` إجباري | `is_verified = '1'` إجباري | + +--- + +## 8. الملخص النهائي + +| البند | النتيجة | +|-------|---------| +| **التدفق العام** | ✅ صحيح — Flutter → connect.php (JWT) → معالجة → رد | +| **connect.php يحمي** | ✅ loginFromGoogle, checkPhone, signup, verifyEmail | +| **JWT + Fingerprint** | ✅ Registration (150s) → Access (1h) مع Pepper | +| **Token Revocation** | ✅ عبر Redis في `login.php` | +| **OTP مشفر** | ✅ `encryptData(token_code)` في verify.php | +| **Rate Limiting** | ✅ على login (5/دقيقة)، OTP request (3/5min)، OTP verify (3/5min) | +| **كشف تغيير الجهاز** | ✅ يقارن FCM token + fingerprint → OTP إذا اختلف | +| **Timing Attack** | ✅ `usleep()` في login.php | +| **تشفير DB** | ✅ جميع PII مشفرة | + +### ⚠️ ملاحظة واحدة + +يُستخدم `GetStorage` لتخزين JWT، وهو غير مشفر. لكنه يُستخدم مع `FlutterSecureStorage` أيضاً. يُفضل الاعتماد على `FlutterSecureStorage` فقط للـ JWT. + +### ✅ الخلاصة + +**تدفق الراكب (Siro Rider) صحيح أمنياً وإجرائياً بالكامل.** +- جميع ملفات Auth محمية عبر `connect.php` الحديث (JWT + Rate Limit) +- OTP مشفر قبل التخزين والمقارنة +- JWT مرتبط بالجهاز عبر Fingerprint + Pepper +- كشف تغيير الجهاز عبر FCM + Fingerprint +- Rate Limiting على جميع نقاط الدخول + +
\ No newline at end of file diff --git a/siro_driver/lib/constant/city_polygons.dart b/siro_driver/lib/constant/city_polygons.dart new file mode 100644 index 0000000..ec57fe4 --- /dev/null +++ b/siro_driver/lib/constant/city_polygons.dart @@ -0,0 +1,37 @@ +import 'package:intaleq_maps/intaleq_maps.dart'; + +class CityPolygons { + // ========================================================== + // 1. مدينة دمشق (المناطق الأساسية والراقية) + // يمكنك تعديل هذه الإحداثيات لتشمل المناطق المطلوبة بدقة أكبر + // ========================================================== + static final List damascusPolygon = [ + const LatLng(33.541, 36.230), // دمر / الشمال الغربي + const LatLng(33.550, 36.330), // القابون / برزة - الشمال الشرقي + const LatLng(33.470, 36.330), // جرمانا / الجنوب الشرقي + const LatLng(33.460, 36.240), // المزة / كفرسوسة - الجنوب الغربي + const LatLng(33.541, 36.230), // العودة لنقطة البداية + ]; + + // ========================================================== + // 2. مدينة عمان (المناطق الحيوية) + // ========================================================== + static final List ammanPolygon = [ + const LatLng(32.040, 35.830), // صويلح / الشمال الغربي + const LatLng(32.040, 36.000), // طبربور / الشمال الشرقي + const LatLng(31.900, 36.000), // سحاب / الجنوب الشرقي + const LatLng(31.900, 35.830), // مرج الحمام / الدوار السابع - الجنوب الغربي + const LatLng(32.040, 35.830), // العودة لنقطة البداية + ]; + + // ========================================================== + // 3. مدينة القاهرة الكبرى + // ========================================================== + static final List cairoPolygon = [ + const LatLng(30.150, 31.150), // إمبابة / الوراق - الشمال الغربي + const LatLng(30.150, 31.400), // مصر الجديدة / مدينة نصر - الشمال الشرقي + const LatLng(29.950, 31.400), // المعادي / التجمع - الجنوب الشرقي + const LatLng(29.950, 31.150), // الهرم / الجيزة - الجنوب الغربي + const LatLng(30.150, 31.150), // العودة لنقطة البداية + ]; +} diff --git a/siro_driver/lib/controller/auth/captin/login_captin_controller.dart b/siro_driver/lib/controller/auth/captin/login_captin_controller.dart index eb4d495..7ac2311 100755 --- a/siro_driver/lib/controller/auth/captin/login_captin_controller.dart +++ b/siro_driver/lib/controller/auth/captin/login_captin_controller.dart @@ -28,6 +28,7 @@ import '../../../views/auth/syria/pending_driver_page.dart'; import '../../firebase/firbase_messge.dart'; import '../../firebase/local_notification.dart'; import '../../firebase/notification_service.dart'; +import '../../functions/country_logic.dart'; import '../../functions/encrypt_decrypt.dart'; import '../../functions/package_info.dart'; import '../../functions/secure_storage.dart'; @@ -66,38 +67,39 @@ class LoginDriverController extends GetxController { @override void onInit() async { - box.write(BoxName.countryCode, 'Syria'); + // كشف الدولة تلقائياً عبر الموقع الجغرافي + await CountryLogic.initializeCountry(); // box.write(BoxName.driverID, '34feffd3fa72d6bee56b'); // await getAppTester(); getJWT(); super.onInit(); } - getAppTester() async { - var res = await CRUD().get( - link: AppLink.getTesterApp, - payload: {'appPlatform': AppInformation.appName}); - // Log.print('res: ${res}'); - if (res != 'failure') { - var d = jsonDecode(res); - isTest = d['message'][0]['isTest']; - // Log.print('isTest: ${isTest}'); - box.write(BoxName.isTest, isTest); + // getAppTester() async { + // var res = await CRUD().get( + // link: AppLink.getTesterApp, + // payload: {'appPlatform': AppInformation.appName}); + // // Log.print('res: ${res}'); + // if (res != 'failure') { + // var d = jsonDecode(res); + // isTest = d['message'][0]['isTest']; + // // Log.print('isTest: ${isTest}'); + // box.write(BoxName.isTest, isTest); - // Log.print('isTest: ${box.read(BoxName.isTest)}'); - update(); - } else { - isTest = 0; - box.write(BoxName.isTest, isTest); - update(); - return false; - } - } + // // Log.print('isTest: ${box.read(BoxName.isTest)}'); + // update(); + // } else { + // isTest = 0; + // box.write(BoxName.isTest, isTest); + // update(); + // return false; + // } + // } - updateAppTester(String appPlatform) async { - await CRUD().post( - link: AppLink.updateTesterApp, payload: {'appPlatform': appPlatform}); - } + // updateAppTester(String appPlatform) async { + // await CRUD().post( + // link: AppLink.updateTesterApp, payload: {'appPlatform': appPlatform}); + // } isPhoneVerified() async { var res = await CRUD().post( @@ -319,17 +321,14 @@ class LoginDriverController extends GetxController { } } - loginWithGoogleCredential(String driverID, email) async { + loginDriver(String driverID, email) async { isloading = true; update(); // await SecurityHelper.performSecurityChecks(); // Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}'); // await getJWT(); - var res = await CRUD().get(link: AppLink.loginFromGoogleCaptin, payload: { - // 'email': email ?? 'yet', - 'id': driverID, - }); - Log.print('loginWithGoogleCredential: ${res}'); + var res = await CRUD().get(link: AppLink.loginFromGoogleCaptin, payload: {}); + Log.print('loginDriver: ${res}'); if (res == 'failure') { await isPhoneVerified(); isloading = false; // <--- أضفت هذا أيضاً @@ -478,10 +477,7 @@ class LoginDriverController extends GetxController { // await SecurityHelper.performSecurityChecks(); // Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}'); - var res = await CRUD().get(link: AppLink.loginFromGoogleCaptin, payload: { - 'email': email ?? 'yet', - 'id': driverID, - }); + var res = await CRUD().get(link: AppLink.loginFromGoogleCaptin, payload: {}); // print('res is $res'); // if (res == 'failure') { @@ -563,32 +559,34 @@ class LoginDriverController extends GetxController { loginUsingCredentialsWithoutGoogle(String password, email) async { isloading = true; - isGoogleLogin = true; update(); - var res = await CRUD() - .get(link: AppLink.loginUsingCredentialsWithoutGoogle, payload: { - 'email': (email), - 'password': password, - }); - box.write(BoxName.emailDriver, (email).toString()); - // print(res); - if (res == 'failure') { - //Failure - if (box.read(BoxName.phoneVerified).toString() == '1') { - // Get.offAll(() => SyrianCardAI()); - Get.offAll(() => RegistrationView()); - } else { - Get.offAll(() => SmsSignupEgypt()); - } - isloading = false; - update(); - } else { - var jsonDecoeded = jsonDecode(res); - var d = jsonDecoeded['data'][0]; - if (jsonDecoeded.isNotEmpty) { - if (jsonDecoeded['status'] == 'success' && - d['is_verified'].toString() == '1') { + try { + var fingerPrint = await DeviceHelper.getDeviceFingerprint(); + var payload = { + 'email': email, + 'password': password, + 'fingerPrint': fingerPrint, + 'aud': AppInformation.appName, + }; + + var response = await http.post( + Uri.parse(AppLink.loginUsingCredentialsWithoutGoogle), + body: payload, + ); + + if (response.statusCode == 200 || response.statusCode == 201) { + var jsonDecoeded = jsonDecode(response.body); + if (jsonDecoeded['status'] == 'success') { + var d = jsonDecoeded['data'][0]; + var jwt = jsonDecoeded['jwt']; + + // حفظ التوكن أولاً + if (jwt != null) { + box.write(BoxName.jwt, c(jwt)); + await storage.write(key: BoxName.jwt, value: c(jwt)); + } + box.write(BoxName.emailDriver, (d['email'])); box.write(BoxName.driverID, (d['id'])); box.write(BoxName.isTest, '1'); @@ -602,83 +600,24 @@ class LoginDriverController extends GetxController { BoxName.nameDriver, '${(d['first_name'])}' ' ${(d['last_name'])}'); - if ((d['model'].toString().contains('دراجه') || - d['make'].toString().contains('دراجه '))) { - if ((d['gender']).toString() == 'Male') { - box.write(BoxName.carTypeOfDriver, 'Scooter'); - } else { - box.write(BoxName.carTypeOfDriver, 'Pink Bike'); - } - } else if (int.parse(d['year'].toString()) > 2017) { - if ((d['gender']).toString() != 'Male') { - box.write(BoxName.carTypeOfDriver, 'Lady'); - } else { - box.write(BoxName.carTypeOfDriver, 'Comfort'); - } - } else if (int.parse(d['year'].toString()) > 2002 && - int.parse(d['year'].toString()) < 2017) { - box.write(BoxName.carTypeOfDriver, 'Speed'); - } else if (int.parse(d['year'].toString()) < 2002) { - box.write(BoxName.carTypeOfDriver, 'Awfar Car'); - } - updateAppTester(AppInformation.appName); - var fingerPrint = await DeviceHelper.getDeviceFingerprint(); await storage.write(key: BoxName.fingerPrint, value: fingerPrint); - var token = await CRUD().get( - link: AppLink.getDriverToken, - payload: {'captain_id': box.read(BoxName.driverID).toString()}); - - if (token != 'failure') { - if ((jsonDecode(token)['data'][0]['token']) != - (box.read(BoxName.tokenDriver))) { - // Get.put(FirebaseMessagesController()).sendNotificationToDriverMAP( - // 'token change'.tr, - // 'change device'.tr, - // (jsonDecode(token)['data'][0]['token']).toString(), - // [], - // 'ding.wav'); - NotificationService.sendNotification( - target: (jsonDecode(token)['data'][0]['token']).toString(), - title: 'token change'.tr, - body: 'token change'.tr, - isTopic: false, // Important: this is a token - tone: 'cancel', - driverList: [], category: 'token change', - ); - Get.defaultDialog( - title: 'you will use this device?'.tr, - middleText: '', - confirm: MyElevatedButton( - title: 'Ok'.tr, - onPressed: () async { - await CRUD() - .post(link: AppLink.addTokensDriver, payload: { - 'token': box.read(BoxName.tokenDriver), - 'captain_id': box.read(BoxName.driverID).toString(), - 'fingerPrint': (fingerPrint).toString() - }); - - Get.back(); - })); - } - } - Get.off(() => HomeCaptain()); - // Get.off(() => LoginCaptin()); } else { - Get.offAll(() => SmsSignupEgypt()); - + mySnackeBarError('Login failed'.tr); isloading = false; update(); } } else { - mySnackeBarError(''); - + mySnackeBarError('Server error'.tr); isloading = false; update(); } + } catch (e) { + mySnackeBarError('Network error'.tr); + isloading = false; + update(); } } diff --git a/siro_driver/lib/controller/auth/captin/phone_helper_controller.dart b/siro_driver/lib/controller/auth/captin/phone_helper_controller.dart index 1122955..0682192 100644 --- a/siro_driver/lib/controller/auth/captin/phone_helper_controller.dart +++ b/siro_driver/lib/controller/auth/captin/phone_helper_controller.dart @@ -17,7 +17,8 @@ class PhoneAuthHelper { // Define your server URLs static final String _sendOtpUrl = '${AppLink.server}/auth/otp/request.php'; static final String _verifyOtpUrl = '${AppLink.server}/auth/otp/verify.php'; - static final String _registerUrl = '${AppLink.server}/auth/syria/driver/register_driver.php'; + static final String _registerUrl = + '${AppLink.server}/auth/syria/driver/register_driver.php'; // removed formatSyrianPhone /// Sends an OTP to the provided phone number. @@ -89,7 +90,7 @@ class PhoneAuthHelper { box.write(BoxName.driverID, driver['id']); box.write(BoxName.emailDriver, driver['email']); - await Get.find().loginWithGoogleCredential( + await Get.find().loginDriver( driver['id'].toString(), driver['email'].toString()); } else { // ✅ رقم الهاتف تم التحقق منه لكن السائق غير مسجل diff --git a/siro_driver/lib/controller/auth/captin/register_captin_controller.dart b/siro_driver/lib/controller/auth/captin/register_captin_controller.dart index 33ce9bf..97855e6 100755 --- a/siro_driver/lib/controller/auth/captin/register_captin_controller.dart +++ b/siro_driver/lib/controller/auth/captin/register_captin_controller.dart @@ -113,7 +113,7 @@ class RegisterCaptainController extends GetxController { backgroundColor: AppColor.greenColor); box.write(BoxName.phoneVerified, '1'); box.write(BoxName.phone, ('+2${phoneController.text}')); - await Get.put(LoginDriverController()).loginWithGoogleCredential( + await Get.put(LoginDriverController()).loginDriver( box.read(BoxName.driverID).toString(), (box.read(BoxName.emailDriver).toString()), ); @@ -229,7 +229,7 @@ class RegisterCaptainController extends GetxController { mySnackbarSuccess('Phone number is already verified'.tr); box.write(BoxName.phoneVerified, '1'); box.write(BoxName.phone, ('+2${phoneController.text}')); - Get.put(LoginDriverController()).loginWithGoogleCredential( + Get.put(LoginDriverController()).loginDriver( box.read(BoxName.driverID).toString(), box.read(BoxName.emailDriver).toString(), ); diff --git a/siro_driver/lib/controller/auth/syria/registration_controller.dart b/siro_driver/lib/controller/auth/syria/registration_controller.dart index ec77502..68c0bc4 100644 --- a/siro_driver/lib/controller/auth/syria/registration_controller.dart +++ b/siro_driver/lib/controller/auth/syria/registration_controller.dart @@ -371,7 +371,8 @@ class RegistrationController extends GetxController { data.forEach((k, v) => request.fields[k] = v); // المهلة الزمنية 120 ثانية لتناسب الاتصالات الضعيفة - final streamed = await request.send().timeout(const Duration(seconds: 120)); + final streamed = + await request.send().timeout(const Duration(seconds: 120)); final res = await http.Response.fromStream(streamed); if (res.statusCode != 200) { @@ -469,7 +470,8 @@ class RegistrationController extends GetxController { } final statusOk = j['status'] == 'success'; - final fileUrl = (j['file_url'] ?? j['message']?['file_url'])?.toString(); + final fileUrl = + (j['file_url'] ?? j['message']?['file_url'])?.toString(); if (resp.statusCode == 200 && statusOk && @@ -512,14 +514,23 @@ class RegistrationController extends GetxController { final isSyria = box.read(BoxName.countryCode) == 'Syria'; - if (idFrontUrl == null || idFrontUrl.isEmpty || - idBackUrl == null || idBackUrl.isEmpty || - driverLicenseUrl == null || driverLicenseUrl.isEmpty || - (isSyria && (driverLicenseBackUrl == null || driverLicenseBackUrl.isEmpty)) || - profilePicUrl == null || profilePicUrl.isEmpty || - carFrontUrl == null || carFrontUrl.isEmpty || - carBackUrl == null || carBackUrl.isEmpty) { - mySnackbarWarning('Please wait for all documents to finish uploading before registering.'.tr); + if (idFrontUrl == null || + idFrontUrl.isEmpty || + idBackUrl == null || + idBackUrl.isEmpty || + driverLicenseUrl == null || + driverLicenseUrl.isEmpty || + (isSyria && + (driverLicenseBackUrl == null || driverLicenseBackUrl.isEmpty)) || + profilePicUrl == null || + profilePicUrl.isEmpty || + carFrontUrl == null || + carFrontUrl.isEmpty || + carBackUrl == null || + carBackUrl.isEmpty) { + mySnackbarWarning( + 'Please wait for all documents to finish uploading before registering.' + .tr); return; } @@ -626,7 +637,8 @@ class RegistrationController extends GetxController { _addField(fields, 'id_front', idFrontUrl); _addField(fields, 'id_back', idBackUrl); _addField(fields, 'driver_license', driverLicenseUrl); - if (isSyria) _addField(fields, 'driver_license_back', driverLicenseBackUrl); + if (isSyria) + _addField(fields, 'driver_license_back', driverLicenseBackUrl); _addField(fields, 'profile_picture', profilePicUrl); _addField(fields, 'criminal_record', criminalRecordUrl); _addField(fields, 'car_license_front', carFrontUrl); @@ -671,7 +683,7 @@ class RegistrationController extends GetxController { final c = Get.isRegistered() ? Get.find() : Get.put(LoginDriverController()); - c.loginWithGoogleCredential(driverID, email); + c.loginDriver(driverID, email); } else { final msg = (json?['message'] ?? 'Registration failed.').toString(); mySnackeBarError(msg); diff --git a/siro_driver/lib/controller/functions/country_logic.dart b/siro_driver/lib/controller/functions/country_logic.dart index c88618f..1891cf4 100644 --- a/siro_driver/lib/controller/functions/country_logic.dart +++ b/siro_driver/lib/controller/functions/country_logic.dart @@ -1,5 +1,11 @@ +import 'package:geolocator/geolocator.dart'; +import 'package:get/get.dart'; +import 'package:intaleq_maps/intaleq_maps.dart'; import 'package:siro_driver/constant/box_name.dart'; +import 'package:siro_driver/constant/country_polygons.dart'; +import 'package:siro_driver/constant/city_polygons.dart'; import 'package:siro_driver/main.dart'; +import '../../print.dart'; class CountryLogic { /// Formats the phone number based on the country's dialing rules. @@ -91,4 +97,99 @@ class CountryLogic { final country = box.read(BoxName.countryCode) ?? 'Syria'; return formatPhone(cleanPhone, country); } + + /// التحقق مما إذا كانت نقطة داخل مضلع (Ray Casting Algorithm) + static bool isPointInPolygon(LatLng point, List polygon) { + int intersections = 0; + for (int i = 0; i < polygon.length; i++) { + final v1 = polygon[i]; + final v2 = polygon[(i + 1) % polygon.length]; + + if ((v1.latitude <= point.latitude && v2.latitude > point.latitude) || + (v2.latitude <= point.latitude && v1.latitude > point.latitude)) { + final t = (point.latitude - v1.latitude) / (v2.latitude - v1.latitude); + if (point.longitude < + v1.longitude + t * (v2.longitude - v1.longitude)) { + intersections++; + } + } + } + return intersections % 2 != 0; + } + + /// كشف الدولة تلقائياً عبر الموقع الجغرافي + /// باستخدام مضلعات الدول المحددة في CountryPolygons + /// تعيد 'Syria' | 'Egypt' | 'Jordan' | null (في حال الفشل) + static Future detectByLocation() async { + try { + bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); + if (!serviceEnabled) { + Log.print('[CountryLogic] GPS غير مفعل'); + return null; + } + + LocationPermission permission = await Geolocator.checkPermission(); + if (permission == LocationPermission.denied) { + permission = await Geolocator.requestPermission(); + if (permission == LocationPermission.denied) { + Log.print('[CountryLogic] رفض الإذن'); + return null; + } + } + if (permission == LocationPermission.deniedForever) return null; + + final pos = await Geolocator.getCurrentPosition( + desiredAccuracy: LocationAccuracy.low, + timeLimit: const Duration(seconds: 8), + ); + + final point = LatLng(pos.latitude, pos.longitude); + + // فحص المضلعات بالترتيب: Jordan → Syria → Egypt + if (isPointInPolygon(point, CountryPolygons.jordanBoundary)) { + return 'Jordan'; + } + if (isPointInPolygon(point, CountryPolygons.syriaBoundary)) { + return 'Syria'; + } + if (isPointInPolygon(point, CountryPolygons.egyptBoundary)) { + return 'Egypt'; + } + + Log.print('[CountryLogic] الموقع خارج جميع المضلعات'); + } catch (e) { + Log.print('[CountryLogic] detectByLocation error: $e'); + } + return null; + } + + /// تهيئة الدولة عند بدء التطبيق + /// إذا لم تكن الدولة محددة مسبقاً → يكشفها عبر الموقع + /// إذا فشل الكشف → Jordan كخيار افتراضي + static Future initializeCountry() async { + final existing = box.read(BoxName.countryCode); + if (existing != null && existing.toString().isNotEmpty) { + Log.print('[CountryLogic] الدولة موجودة مسبقاً: $existing'); + return; + } + + Log.print('[CountryLogic] كشف الدولة عبر الموقع...'); + final country = await detectByLocation(); + + if (country != null) { + box.write(BoxName.countryCode, country); + Log.print('[CountryLogic] تم الكشف: $country'); + } else { + box.write(BoxName.countryCode, 'Jordan'); + Log.print('[CountryLogic] افتراضي: Jordan'); + } + } + + /// إرجاع مضلع المدينة بناءً على الدولة الحالية + static List getCurrentCityPolygon() { + final country = box.read(BoxName.countryCode) ?? 'Jordan'; + if (country == 'Syria') return CityPolygons.damascusPolygon; + if (country == 'Egypt') return CityPolygons.cairoPolygon; + return CityPolygons.ammanPolygon; + } } diff --git a/siro_driver/lib/controller/functions/gemeni.dart b/siro_driver/lib/controller/functions/gemeni.dart index 541f5d6..7c05858 100755 --- a/siro_driver/lib/controller/functions/gemeni.dart +++ b/siro_driver/lib/controller/functions/gemeni.dart @@ -189,7 +189,7 @@ class AI extends GetxController { // DeviceController().getDeviceSerialNumber(); box.write(BoxName.phoneVerified, true); - Get.find().loginWithGoogleCredential( + Get.find().loginDriver( box.read(BoxName.driverID).toString(), box.read(BoxName.emailDriver).toString(), ); diff --git a/siro_driver/lib/controller/functions/sms_egypt_controller.dart b/siro_driver/lib/controller/functions/sms_egypt_controller.dart index fcb0f31..0142af8 100755 --- a/siro_driver/lib/controller/functions/sms_egypt_controller.dart +++ b/siro_driver/lib/controller/functions/sms_egypt_controller.dart @@ -46,7 +46,7 @@ class SmsEgyptController extends GetxController { ('+2${Get.find().phoneController.text}')); box.write(BoxName.phoneVerified, '1'); - await Get.put(LoginDriverController()).loginWithGoogleCredential( + await Get.put(LoginDriverController()).loginDriver( box.read(BoxName.driverID).toString(), (box.read(BoxName.emailDriver).toString()), ); diff --git a/siro_driver/lib/controller/home/splash_screen_controlle.dart b/siro_driver/lib/controller/home/splash_screen_controlle.dart index b8b3341..6a210ec 100755 --- a/siro_driver/lib/controller/home/splash_screen_controlle.dart +++ b/siro_driver/lib/controller/home/splash_screen_controlle.dart @@ -130,7 +130,7 @@ class SplashScreenController extends GetxController await initializer.initializeApp(); await EncryptionHelper.initialize(); - await loginController.loginWithGoogleCredential( + await loginController.loginDriver( box.read(BoxName.driverID).toString(), box.read(BoxName.emailDriver).toString(), ); diff --git a/siro_driver/lib/views/auth/captin/otp_page.dart b/siro_driver/lib/views/auth/captin/otp_page.dart index ece58cb..bb94aa2 100644 --- a/siro_driver/lib/views/auth/captin/otp_page.dart +++ b/siro_driver/lib/views/auth/captin/otp_page.dart @@ -118,7 +118,7 @@ class AuthScreen extends StatelessWidget { const Text('Login', style: TextStyle(color: Colors.white)), onPressed: () { if (testerFormKey.currentState!.validate()) { - controller.logintest( + controller.loginUsingCredentialsWithoutGoogle( testerPasswordController.text.trim(), testerEmailController.text.trim(), ); diff --git a/siro_driver/lib/views/home/Captin/home_captain/osm_view_map.dart b/siro_driver/lib/views/home/Captin/home_captain/osm_view_map.dart index 6c8ef3a..67c2ad1 100644 --- a/siro_driver/lib/views/home/Captin/home_captain/osm_view_map.dart +++ b/siro_driver/lib/views/home/Captin/home_captain/osm_view_map.dart @@ -5,6 +5,7 @@ import 'package:intaleq_maps/intaleq_maps.dart'; import '../../../../constant/api_key.dart'; import '../../../../controller/functions/location_controller.dart'; import '../../../../controller/home/captin/home_captain_controller.dart'; +import '../../../../controller/functions/country_logic.dart'; class OsmMapView extends StatelessWidget { const OsmMapView({super.key}); @@ -22,27 +23,77 @@ class OsmMapView extends StatelessWidget { locationController.myLocation.longitude); final double currentHeading = locationController.heading; - return IntaleqMap( - apiKey: AK.mapSaasKey, - initialCameraPosition: CameraPosition( - target: currentLocation, - zoom: 15, - bearing: currentHeading, - ), - markers: { - Marker( - markerId: const MarkerId('myLocation'), - position: currentLocation, - rotation: currentHeading, - icon: homeCaptainController.carIcon, - anchor: const Offset(0.5, 0.5), - flat: true, - zIndex: 2, - ) - }, - onMapCreated: (IntaleqMapController controller) { - // You can assign this controller if needed - }, + final cityPolygon = CountryLogic.getCurrentCityPolygon(); + final bool inServiceArea = CountryLogic.isPointInPolygon(currentLocation, cityPolygon); + + return Stack( + children: [ + IntaleqMap( + apiKey: AK.mapSaasKey, + initialCameraPosition: CameraPosition( + target: currentLocation, + zoom: 15, + bearing: currentHeading, + ), + markers: { + Marker( + markerId: const MarkerId('myLocation'), + position: currentLocation, + rotation: currentHeading, + icon: homeCaptainController.carIcon, + anchor: const Offset(0.5, 0.5), + flat: true, + zIndex: 2, + ) + }, + polygons: { + Polygon( + polygonId: const PolygonId('city_polygon'), + points: cityPolygon, + fillColor: Colors.green.withOpacity(0.15), + strokeColor: Colors.green[800]!, + strokeWidth: 2, + ) + }, + onMapCreated: (IntaleqMapController controller) { + // You can assign this controller if needed + }, + ), + if (!inServiceArea) + Positioned( + bottom: 20, + left: 20, + right: 20, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + decoration: BoxDecoration( + color: Colors.redAccent.withOpacity(0.9), + borderRadius: BorderRadius.circular(12), + boxShadow: const [ + BoxShadow(color: Colors.black26, blurRadius: 4, offset: Offset(0, 2)) + ], + ), + child: const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.warning_amber_rounded, color: Colors.white), + SizedBox(width: 8), + Expanded( + child: Text( + 'الخدمة غير متوفرة في منطقتك حالياً', + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ), + ], + ), + ), + ), + ], ); }); } diff --git a/siro_rider/lib/constant/links.dart b/siro_rider/lib/constant/links.dart index e71e59e..3d2bc7a 100644 --- a/siro_rider/lib/constant/links.dart +++ b/siro_rider/lib/constant/links.dart @@ -398,6 +398,7 @@ static String get updateTesterApp => "$auth/Tester/updateTesterApp.php"; static String get signUp => "$auth/signup.php"; static String get sendVerifyEmail => "$auth/sendVerifyEmail.php"; static String get loginFromGooglePassenger => "$auth/loginFromGooglePassenger.php"; +static String get loginUsingCredentialsWithoutGooglePassenger => "$auth/loginUsingCredentialsWithoutGooglePassenger.php"; static String get checkPhoneNumberISVerfiedPassenger => "$auth/checkPhoneNumberISVerfiedPassenger.php"; diff --git a/siro_rider/lib/controller/auth/login_controller.dart b/siro_rider/lib/controller/auth/login_controller.dart index f2f9ded..2eb3166 100644 --- a/siro_rider/lib/controller/auth/login_controller.dart +++ b/siro_rider/lib/controller/auth/login_controller.dart @@ -25,6 +25,7 @@ import 'package:location/location.dart'; import '../../env/env.dart'; import '../../print.dart'; import '../../views/auth/otp_token_page.dart'; +import '../functions/country_logic.dart'; import '../functions/encrypt_decrypt.dart'; import '../functions/package_info.dart'; @@ -48,7 +49,8 @@ class LoginController extends GetxController { @override void onInit() async { // Log.print('box.read(BoxName.isTest): ${box.read(BoxName.isTest)}'); - box.write(BoxName.countryCode, 'Syria'); + // كشف الدولة تلقائياً عبر الموقع الجغرافي + await CountryLogic.initializeCountry(); FirebaseMessagesController().getToken(); super.onInit(); } @@ -271,8 +273,6 @@ class LoginController extends GetxController { final res = await CRUD().get( link: AppLink.loginFromGooglePassenger, payload: { - 'email': email, - 'id': passengerID, // استخدم المعامل مباشرة 'platform': Platform.isAndroid ? 'android' : 'ios', 'appName': AppInformation.appName, }, @@ -385,7 +385,7 @@ class LoginController extends GetxController { "inviteCode": invite, "passengerID": passengerID, }); - + // سجل الدعوة أيضاً في النظام الموحد الجديد await CRUD().post(link: AppLink.addUnifiedInvite, payload: { "inviter_code": invite, @@ -445,46 +445,48 @@ class LoginController extends GetxController { // ); // } - void login() async { + void loginTester() async { isloading = true; update(); - var res = - await CRUD().get(link: AppLink.loginFromGooglePassenger, payload: { - 'email': (emailController.text), - 'id': passwordController.text, - "platform": Platform.isAndroid ? 'android' : 'ios', - "appName": AppInformation.appName, - }); + + try { + var fingerPrint = await DeviceHelper.getDeviceFingerprint(); + var payload = { + 'email': emailController.text.trim(), + 'password': passwordController.text.trim(), + 'fingerPrint': fingerPrint, + 'aud': '${AK.allowed}${Platform.isAndroid ? 'android' : 'ios'}', + }; + + var response = await http.post( + Uri.parse(AppLink.loginUsingCredentialsWithoutGooglePassenger), + body: payload, + ); + + if (response.statusCode == 200 || response.statusCode == 201) { + var jsonDecoeded = jsonDecode(response.body); + if (jsonDecoeded['status'] == 'success' && jsonDecoeded['data'][0]['verified'].toString() == '1') { + var d = jsonDecoeded['data'][0]; + var jwt = jsonDecoeded['jwt']; + + // حفظ التوكن + if (jwt != null) { + box.write(BoxName.jwt, c(jwt)); + await storage.write(key: BoxName.jwt, value: c(jwt)); + } - isloading = false; - update(); - if (res == 'Failure') { - //Failure - Get.offAll(() => LoginPage()); - isloading = false; - update(); - // Get.snackbar("User does not exist.".tr, '', backgroundColor: Colors.red); - } else { - var jsonDecoeded = jsonDecode(res); - if (jsonDecoeded.isNotEmpty) { - if (jsonDecoeded['status'] == 'success' && - jsonDecoeded['data'][0]['verified'].toString() == '1') { - // box.write(BoxName.isVerified, '1'); - box.write(BoxName.email, jsonDecoeded['data'][0]['email']); - box.write(BoxName.name, jsonDecoeded['data'][0]['first_name']); - box.write(BoxName.phone, jsonDecoeded['data'][0]['phone']); - box.write(BoxName.passengerID, passwordController.text); - // var token = await CRUD().get(link: AppLink.getTokens, payload: { - // 'passengerID': box.read(BoxName.passengerID).toString() - // }); - // await updateAppTester(AppInformation.appName); + box.write(BoxName.email, d['email']); + box.write(BoxName.name, d['first_name']); + box.write(BoxName.phone, d['phone']); + box.write(BoxName.passengerID, d['id']); + box.write(BoxName.isTest, '1'); + + await storage.write(key: BoxName.fingerPrint, value: fingerPrint); Get.offAll(() => const MapPagePassenger()); } else { - // Get.offAll(() => SmsSignupEgypt()); - // Get.snackbar(jsonDecoeded['status'], jsonDecoeded['data'], - // backgroundColor: Colors.redAccent); + Get.offAll(() => LoginPage()); isloading = false; update(); } @@ -492,6 +494,10 @@ class LoginController extends GetxController { isloading = false; update(); } + } catch (e) { + Log.print("Tester Login Error: $e"); + isloading = false; + update(); } } diff --git a/siro_rider/lib/controller/functions/country_logic.dart b/siro_rider/lib/controller/functions/country_logic.dart index ce12c0a..fdde578 100644 --- a/siro_rider/lib/controller/functions/country_logic.dart +++ b/siro_rider/lib/controller/functions/country_logic.dart @@ -1,5 +1,9 @@ +import 'package:geolocator/geolocator.dart'; +import 'package:intaleq_maps/intaleq_maps.dart'; import 'package:siro_rider/constant/box_name.dart'; +import 'package:siro_rider/constant/country_polygons.dart'; import 'package:siro_rider/main.dart'; +import '../../print.dart'; class CountryLogic { /// Formats the phone number based on the country's dialing rules. @@ -88,7 +92,77 @@ class CountryLogic { return formatPhone(cleanPhone, 'Jordan'); } - final country = box.read(BoxName.countryCode) ?? 'Syria'; + final country = box.read(BoxName.countryCode) ?? 'Jordan'; return formatPhone(cleanPhone, country); } + + /// التحقق مما إذا كانت نقطة داخل مضلع (Ray Casting Algorithm) + static bool _isPointInPolygon(LatLng point, List polygon) { + int intersections = 0; + for (int i = 0; i < polygon.length; i++) { + final v1 = polygon[i]; + final v2 = polygon[(i + 1) % polygon.length]; + if ((v1.latitude <= point.latitude && v2.latitude > point.latitude) || + (v2.latitude <= point.latitude && v1.latitude > point.latitude)) { + final t = (point.latitude - v1.latitude) / (v2.latitude - v1.latitude); + if (point.longitude < + v1.longitude + t * (v2.longitude - v1.longitude)) { + intersections++; + } + } + } + return intersections % 2 != 0; + } + + /// كشف الدولة تلقائياً عبر الموقع الجغرافي باستخدام مضلعات CountryPolygons + static Future detectByLocation() async { + try { + bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); + if (!serviceEnabled) return null; + + LocationPermission permission = await Geolocator.checkPermission(); + if (permission == LocationPermission.denied) { + permission = await Geolocator.requestPermission(); + if (permission == LocationPermission.denied) return null; + } + if (permission == LocationPermission.deniedForever) return null; + + final pos = await Geolocator.getCurrentPosition( + desiredAccuracy: LocationAccuracy.low, + timeLimit: const Duration(seconds: 8), + ); + final point = LatLng(pos.latitude, pos.longitude); + + if (_isPointInPolygon(point, CountryPolygons.jordanBoundary)) { + return 'Jordan'; + } + if (_isPointInPolygon(point, CountryPolygons.syriaBoundary)) { + return 'Syria'; + } + if (_isPointInPolygon(point, CountryPolygons.egyptBoundary)) { + return 'Egypt'; + } + } catch (e) { + Log.print('[CountryLogic] detectByLocation error: $e'); + } + return null; + } + + /// تهيئة الدولة عند بدء التطبيق + static Future initializeCountry() async { + final existing = box.read(BoxName.countryCode); + if (existing != null && existing.toString().isNotEmpty) { + Log.print('[CountryLogic] الدولة موجودة مسبقاً: $existing'); + return; + } + Log.print('[CountryLogic] كشف الدولة عبر الموقع...'); + final country = await detectByLocation(); + if (country != null) { + box.write(BoxName.countryCode, country); + Log.print('[CountryLogic] تم الكشف: $country'); + } else { + box.write(BoxName.countryCode, 'Jordan'); + Log.print('[CountryLogic] افتراضي: Jordan'); + } + } } diff --git a/siro_rider/lib/controller/functions/crud.dart b/siro_rider/lib/controller/functions/crud.dart index 265c761..41ee99a 100644 --- a/siro_rider/lib/controller/functions/crud.dart +++ b/siro_rider/lib/controller/functions/crud.dart @@ -75,14 +75,39 @@ class CRUD { } // ───────────────────────────────────────────────────────────── - // دالة مساعدة خاصة: يجيب البصمة المشفرة من GetStorage - // هي نفس القيمة المرسلة في login وعُملها hash في JWT payload - // السيرفر يعمل: sha256(X-Device-FP + FP_PEPPER) == JWT.fingerPrint + // دالة مساعدة خاصة: تجيب البصمة المشفرة من GetStorage // ───────────────────────────────────────────────────────────── String _getFpHeader() { return box.read(BoxName.deviceFpEncrypted)?.toString() ?? ''; } + // ───────────────────────────────────────────────────────────── + // دالة مساعدة خاصة: تقرأ JWT من FlutterSecureStorage (آمن) + // بدلاً من GetStorage (غير مشفر) + // ───────────────────────────────────────────────────────────── + Future _getJwt() async { + try { + final String? encryptedJwt = await storage.read(key: BoxName.jwt); + if (encryptedJwt == null || encryptedJwt.isEmpty) { + // Fallback إلى GetStorage للتوافقية + final String? fallback = box.read(BoxName.jwt); + if (fallback != null) { + return r(fallback).toString().split(Env.addd)[0]; + } + return ''; + } + return r(encryptedJwt).toString().split(Env.addd)[0]; + } catch (e) { + Log.print('Error reading JWT from SecureStorage: $e'); + // Fallback + final String? fallback = box.read(BoxName.jwt); + if (fallback != null) { + return r(fallback).toString().split(Env.addd)[0]; + } + return ''; + } + } + /// Centralized private method to handle all API requests. /// Includes retry logic, network checking, and standardized error handling. Future _makeRequest({ @@ -169,7 +194,7 @@ class CRUD { required String link, Map? payload, }) async { - String token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]; + final token = await _getJwt(); final headers = { 'Content-Type': 'application/x-www-form-urlencoded', @@ -193,14 +218,14 @@ class CRUD { required String link, Map? payload, }) async { + final token = await _getJwt(); var url = Uri.parse(link); var response = await http.post( url, body: payload, headers: { 'Content-Type': 'application/x-www-form-urlencoded', - 'Authorization': - 'Bearer ${r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]}', + 'Authorization': 'Bearer $token', 'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز }, ); @@ -548,7 +573,6 @@ class CRUD { return response.statusCode; } - Future postPayMob({ required String link, Map? payload, diff --git a/siro_rider/lib/controller/functions/secure_storage.dart b/siro_rider/lib/controller/functions/secure_storage.dart index baba236..095016a 100644 --- a/siro_rider/lib/controller/functions/secure_storage.dart +++ b/siro_rider/lib/controller/functions/secure_storage.dart @@ -4,12 +4,9 @@ import 'package:siro_rider/constant/box_name.dart'; import 'package:siro_rider/constant/info.dart'; import 'package:siro_rider/controller/auth/login_controller.dart'; import 'package:jwt_decoder/jwt_decoder.dart'; -import 'package:secure_string_operations/secure_string_operations.dart'; -import '../../constant/char_map.dart'; import '../../constant/links.dart'; import '../../main.dart'; -import '../../print.dart'; import 'crud.dart'; import 'encrypt_decrypt.dart'; @@ -24,23 +21,25 @@ class SecureStorage { } } -const List keysToFetch = [ - 'serverPHP', - // 'seferAlexandriaServer', - // 'seferPaymentServer', - // 'seferCairoServer', - // 'seferGizaServer', -]; - class AppInitializer { List> links = []; + /// تقرأ JWT من FlutterSecureStorage أولاً، مع fallback إلى GetStorage + Future _readJwt() async { + try { + final secureJwt = await storage.read(key: 'jwt'); + if (secureJwt != null && secureJwt.isNotEmpty) return secureJwt; + } catch (_) {} + return box.read(BoxName.jwt); + } + Future initializeApp() async { - if (box.read(BoxName.jwt) == null) { + final jwt = await _readJwt(); + if (jwt == null) { await LoginController().getJWT(); } else { - bool isTokenExpired = JwtDecoder.isExpired( - r(box.read(BoxName.jwt)).toString().split(AppInformation.addd)[0]); + bool isTokenExpired = + JwtDecoder.isExpired(r(jwt).toString().split(AppInformation.addd)[0]); if (isTokenExpired) { await LoginController().getJWT(); diff --git a/siro_rider/lib/views/auth/login_page.dart b/siro_rider/lib/views/auth/login_page.dart index 4458628..fde7c86 100644 --- a/siro_rider/lib/views/auth/login_page.dart +++ b/siro_rider/lib/views/auth/login_page.dart @@ -419,7 +419,7 @@ class LoginPage extends StatelessWidget { onPressed: () { if (controller.formKey.currentState! .validate()) { - controller.login(); + controller.loginTester(); } }, ), diff --git a/siro_rider/lib/views/auth/otp_page.dart b/siro_rider/lib/views/auth/otp_page.dart index ef8c567..0a309df 100644 --- a/siro_rider/lib/views/auth/otp_page.dart +++ b/siro_rider/lib/views/auth/otp_page.dart @@ -160,7 +160,7 @@ class AuthScreen extends StatelessWidget { testerEmailController.text; controller.passwordController.text = testerPasswordController.text; - controller.login(); + controller.loginTester(); Navigator.of(dialogContext).pop(); } },