Update: 2026-06-12 22:40:40
This commit is contained in:
@@ -4,19 +4,11 @@ require_once __DIR__ . '/../../connect.php';
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
/* ────────────────────────────────
|
/* ────────────────────────────────
|
||||||
1) قراءة القيم الأولية
|
1) استخدام ID من التوكن (JWT)
|
||||||
───────────────────────────────── */
|
───────────────────────────────── */
|
||||||
// $emailRaw = filterRequest('email'); // البريد القادم من التطبيق (غير مشفَّر)
|
$driverID = $user_id;
|
||||||
$driverID = filterRequest('id'); // DriverID المُرسل
|
|
||||||
|
|
||||||
// error_log("[Debug] Email (raw): $emailRaw");
|
error_log("[Debug] DriverID from JWT: $driverID");
|
||||||
error_log("[Debug] DriverID: $driverID");
|
|
||||||
|
|
||||||
/* ────────────────────────────────
|
|
||||||
2) تشفير الإيميل
|
|
||||||
───────────────────────────────── */
|
|
||||||
// $emailEnc = $encryptionHelper->encryptData($emailRaw);
|
|
||||||
// error_log("[Debug] Email (encrypted): $emailEnc");
|
|
||||||
|
|
||||||
/* ────────────────────────────────
|
/* ────────────────────────────────
|
||||||
3) إعداد الاستعلام الموحَّد
|
3) إعداد الاستعلام الموحَّد
|
||||||
@@ -36,7 +28,6 @@ try {
|
|||||||
LEFT JOIN CarRegistration ON CarRegistration.driverID = driver.id
|
LEFT JOIN CarRegistration ON CarRegistration.driverID = driver.id
|
||||||
LEFT JOIN invites inv ON inv.driverId = driver.id
|
LEFT JOIN invites inv ON inv.driverId = driver.id
|
||||||
WHERE
|
WHERE
|
||||||
|
|
||||||
driver.id = :id
|
driver.id = :id
|
||||||
-- AND phone_verification.is_verified = '1'
|
-- AND phone_verification.is_verified = '1'
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
@@ -48,7 +39,6 @@ try {
|
|||||||
|
|
||||||
// باراميترات الربط
|
// باراميترات الربط
|
||||||
$params = [
|
$params = [
|
||||||
//':email' => $emailEnc,
|
|
||||||
':id' => $driverID,
|
':id' => $driverID,
|
||||||
];
|
];
|
||||||
foreach ($params as $k => $v) {
|
foreach ($params as $k => $v) {
|
||||||
|
|||||||
@@ -1,77 +1,94 @@
|
|||||||
<?php
|
<?php
|
||||||
|
// ============================================================
|
||||||
|
// loginUsingCredentialsWithoutGoogle.php
|
||||||
|
// مخصص لدخول الفاحصين (Testers) بالإيميل والباسورد
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
require_once __DIR__ . '/../../connect.php';
|
require_once __DIR__ . '/../../core/bootstrap.php';
|
||||||
|
|
||||||
$email = filterRequest('email');
|
$email = filterRequest('email');
|
||||||
$password = filterRequest('password');
|
$password = filterRequest('password');
|
||||||
|
$audience = filterRequest('aud') ?? 'siro-driver-android'; // الافتراضي
|
||||||
|
$fingerprint = filterRequest('fingerPrint') ?? filterRequest('fingerprint');
|
||||||
|
|
||||||
// تشفير الإيميل لاستخدامه في الاستعلام
|
// تشفير الإيميل لاستخدامه في الاستعلام
|
||||||
$encryptedEmail = $encryptionHelper->encryptData($email);
|
$encryptedEmail = $encryptionHelper->encryptData($email);
|
||||||
|
|
||||||
// SQL لاسترجاع المستخدم بناءً على البريد الإلكتروني المشفر
|
try {
|
||||||
$sql = "SELECT
|
$con = Database::get('main');
|
||||||
driver.id,
|
|
||||||
driver.phone,
|
// SQL لاسترجاع المستخدم بناءً على البريد الإلكتروني المشفر
|
||||||
driver.email,
|
$sql = "SELECT
|
||||||
driver.gender,
|
driver.id,
|
||||||
driver.birthdate,
|
driver.phone,
|
||||||
driver.site,
|
driver.email,
|
||||||
driver.first_name,
|
driver.gender,
|
||||||
driver.last_name,
|
driver.birthdate,
|
||||||
driver.bankCode,
|
driver.site,
|
||||||
driver.accountBank,
|
driver.first_name,
|
||||||
driver.education,
|
driver.last_name,
|
||||||
driver.employmentType,
|
driver.bankCode,
|
||||||
driver.maritalStatus,
|
driver.accountBank,
|
||||||
driver.created_at,
|
driver.employmentType,
|
||||||
driver.updated_at,
|
driver.maritalStatus,
|
||||||
driver.password,
|
driver.created_at,
|
||||||
phone_verification.is_verified,
|
driver.updated_at,
|
||||||
CarRegistration.make,
|
driver.password,
|
||||||
CarRegistration.model,
|
phone_verification.is_verified,
|
||||||
CarRegistration.year
|
CarRegistration.make,
|
||||||
FROM
|
CarRegistration.model,
|
||||||
driver
|
CarRegistration.year
|
||||||
LEFT JOIN phone_verification ON phone_verification.phone_number = driver.phone
|
FROM driver
|
||||||
LEFT JOIN CarRegistration ON CarRegistration.driverID = driver.id
|
LEFT JOIN phone_verification ON phone_verification.phone_number = driver.phone
|
||||||
WHERE
|
LEFT JOIN CarRegistration ON CarRegistration.driverID = driver.id
|
||||||
driver.email = :email AND phone_verification.is_verified = '1'
|
WHERE
|
||||||
LIMIT 1";
|
driver.email = :email
|
||||||
|
LIMIT 1";
|
||||||
|
|
||||||
$stmt = $con->prepare($sql);
|
$stmt = $con->prepare($sql);
|
||||||
$stmt->bindParam(':email', $encryptedEmail);
|
$stmt->bindParam(':email', $encryptedEmail);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
|
|
||||||
$data = $stmt->fetch(PDO::FETCH_ASSOC);
|
$data = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
if ($data) {
|
if ($data) {
|
||||||
if (password_verify($password, $data['password'])) {
|
// فحص الباسورد (في نظامنا، يمكن أن يكون الباسورد هو HMAC أو نص عادي للفاحصين)
|
||||||
unset($data['password']);
|
// لنفترض أن الفاحص له باسورد عادي أو مشفر بـ bcrypt
|
||||||
|
if (password_verify($password, $data['password']) || $password === $data['password']) {
|
||||||
|
unset($data['password']);
|
||||||
|
|
||||||
// فك تشفير الحقول الحساسة
|
// فك تشفير الحقول الحساسة
|
||||||
$data['phone'] = $encryptionHelper->decryptData($data['phone']);
|
$data['phone'] = $encryptionHelper->decryptData($data['phone']);
|
||||||
$data['email'] = $encryptionHelper->decryptData($data['email']);
|
$data['email'] = $encryptionHelper->decryptData($data['email']);
|
||||||
$data['gender'] = $encryptionHelper->decryptData($data['gender']);
|
$data['gender'] = $encryptionHelper->decryptData($data['gender']);
|
||||||
$data['birthdate'] = $encryptionHelper->decryptData($data['birthdate']);
|
$data['birthdate'] = $encryptionHelper->decryptData($data['birthdate']);
|
||||||
$data['site'] = $encryptionHelper->decryptData($data['site']);
|
$data['site'] = $encryptionHelper->decryptData($data['site']);
|
||||||
$data['first_name'] = $encryptionHelper->decryptData($data['first_name']);
|
$data['first_name'] = $encryptionHelper->decryptData($data['first_name']);
|
||||||
$data['last_name'] = $encryptionHelper->decryptData($data['last_name']);
|
$data['last_name'] = $encryptionHelper->decryptData($data['last_name']);
|
||||||
$data['education'] = $encryptionHelper->decryptData($data['education']);
|
if(isset($data['employmentType'])) $data['employmentType'] = $encryptionHelper->decryptData($data['employmentType']);
|
||||||
$data['employmentType'] = $encryptionHelper->decryptData($data['employmentType']);
|
if(isset($data['maritalStatus'])) $data['maritalStatus'] = $encryptionHelper->decryptData($data['maritalStatus']);
|
||||||
$data['maritalStatus'] = $encryptionHelper->decryptData($data['maritalStatus']);
|
|
||||||
|
|
||||||
echo json_encode([
|
// توليد الـ JWT بصلاحية (tester) لتميزهم عن السائقين الفعليين
|
||||||
"status" => "success",
|
$jwtService = new JwtService($redis);
|
||||||
"data" => $data
|
$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 {
|
} else {
|
||||||
jsonError("Incorrect password.");
|
jsonError("User does not exist.");
|
||||||
}
|
}
|
||||||
} else {
|
} catch (Exception $e) {
|
||||||
jsonError("User does not exist or phone number not verified.");
|
error_log("[Tester Login Error] " . $e->getMessage());
|
||||||
|
jsonError("Server error occurred.");
|
||||||
|
} finally {
|
||||||
|
$stmt = null;
|
||||||
|
$con = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt = null;
|
|
||||||
$con = null;
|
|
||||||
exit();
|
exit();
|
||||||
?>
|
?>
|
||||||
@@ -2,14 +2,12 @@
|
|||||||
|
|
||||||
require_once __DIR__ . '/../connect.php';
|
require_once __DIR__ . '/../connect.php';
|
||||||
|
|
||||||
// استدعاء المعاملات
|
// لا نستقبل id أو email من التطبيق بل نأخذهم من التوكن (JWT) لزيادة الأمان
|
||||||
$email = filterRequest('email');
|
|
||||||
$id = filterRequest('id');
|
|
||||||
$platform = filterRequest("platform") ?: 'unknown';
|
$platform = filterRequest("platform") ?: 'unknown';
|
||||||
$appName = filterRequest("appName") ?: 'unknown';
|
$appName = filterRequest("appName") ?: 'unknown';
|
||||||
|
|
||||||
// تشفير الإيميل لأنه يُرسل من التطبيق غير مشفّر
|
// الاعتماد كلياً على الـ ID المستخرج من JWT داخل connect.php
|
||||||
$email = $encryptionHelper->encryptData($email);
|
$id = $user_id;
|
||||||
|
|
||||||
// تجهيز الاستعلام
|
// تجهيز الاستعلام
|
||||||
$sql = "SELECT
|
$sql = "SELECT
|
||||||
@@ -47,12 +45,11 @@ LEFT JOIN promos
|
|||||||
ON promos.passengerID = p.id
|
ON promos.passengerID = p.id
|
||||||
LEFT JOIN tokens t
|
LEFT JOIN tokens t
|
||||||
ON t.passengerID = p.id
|
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";
|
LIMIT 1";
|
||||||
|
|
||||||
// تنفيذ الاستعلام
|
// تنفيذ الاستعلام
|
||||||
$stmt = $con->prepare($sql);
|
$stmt = $con->prepare($sql);
|
||||||
$stmt->bindParam(':email', $email);
|
|
||||||
$stmt->bindParam(':id', $id);
|
$stmt->bindParam(':id', $id);
|
||||||
$stmt->bindParam(':appName', $appName);
|
$stmt->bindParam(':appName', $appName);
|
||||||
$stmt->bindParam(':platform', $platform);
|
$stmt->bindParam(':platform', $platform);
|
||||||
|
|||||||
96
backend/auth/loginUsingCredentialsWithoutGooglePassenger.php
Normal file
96
backend/auth/loginUsingCredentialsWithoutGooglePassenger.php
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<?php
|
||||||
|
// loginUsingCredentialsWithoutGooglePassenger.php
|
||||||
|
// مسار مخصص لفاحصي التطبيق (الركاب) يعمل بدون JWT Interceptors
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../core/bootstrap.php';
|
||||||
|
|
||||||
|
$email = filterRequest("email");
|
||||||
|
$password = filterRequest("password");
|
||||||
|
$fingerprint = filterRequest('fingerPrint') ?? filterRequest('fingerprint');
|
||||||
|
$audience = filterRequest('aud') ?: 'siro_passenger';
|
||||||
|
|
||||||
|
if (!$email || !$password) {
|
||||||
|
echo json_encode(["status" => "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();
|
||||||
291
country_multi_simulation_report.md
Normal file
291
country_multi_simulation_report.md
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
<div dir="rtl" lang="ar">
|
||||||
|
|
||||||
|
# محاكاة النظام لثلاث دول — الأردن، مصر، سوريا
|
||||||
|
|
||||||
|
## تحليل شامل لتدفق المستخدمين بين الدول
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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<Future> 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).
|
||||||
|
|
||||||
|
</div>
|
||||||
258
rider_auth_flow_analysis.md
Normal file
258
rider_auth_flow_analysis.md
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
<div dir="rtl" lang="ar">
|
||||||
|
|
||||||
|
# تحليل تدفق الراكب (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 على جميع نقاط الدخول
|
||||||
|
|
||||||
|
</div>
|
||||||
37
siro_driver/lib/constant/city_polygons.dart
Normal file
37
siro_driver/lib/constant/city_polygons.dart
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import 'package:intaleq_maps/intaleq_maps.dart';
|
||||||
|
|
||||||
|
class CityPolygons {
|
||||||
|
// ==========================================================
|
||||||
|
// 1. مدينة دمشق (المناطق الأساسية والراقية)
|
||||||
|
// يمكنك تعديل هذه الإحداثيات لتشمل المناطق المطلوبة بدقة أكبر
|
||||||
|
// ==========================================================
|
||||||
|
static final List<LatLng> 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<LatLng> 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<LatLng> 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), // العودة لنقطة البداية
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ import '../../../views/auth/syria/pending_driver_page.dart';
|
|||||||
import '../../firebase/firbase_messge.dart';
|
import '../../firebase/firbase_messge.dart';
|
||||||
import '../../firebase/local_notification.dart';
|
import '../../firebase/local_notification.dart';
|
||||||
import '../../firebase/notification_service.dart';
|
import '../../firebase/notification_service.dart';
|
||||||
|
import '../../functions/country_logic.dart';
|
||||||
import '../../functions/encrypt_decrypt.dart';
|
import '../../functions/encrypt_decrypt.dart';
|
||||||
import '../../functions/package_info.dart';
|
import '../../functions/package_info.dart';
|
||||||
import '../../functions/secure_storage.dart';
|
import '../../functions/secure_storage.dart';
|
||||||
@@ -66,38 +67,39 @@ class LoginDriverController extends GetxController {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() async {
|
void onInit() async {
|
||||||
box.write(BoxName.countryCode, 'Syria');
|
// كشف الدولة تلقائياً عبر الموقع الجغرافي
|
||||||
|
await CountryLogic.initializeCountry();
|
||||||
// box.write(BoxName.driverID, '34feffd3fa72d6bee56b');
|
// box.write(BoxName.driverID, '34feffd3fa72d6bee56b');
|
||||||
// await getAppTester();
|
// await getAppTester();
|
||||||
getJWT();
|
getJWT();
|
||||||
super.onInit();
|
super.onInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
getAppTester() async {
|
// getAppTester() async {
|
||||||
var res = await CRUD().get(
|
// var res = await CRUD().get(
|
||||||
link: AppLink.getTesterApp,
|
// link: AppLink.getTesterApp,
|
||||||
payload: {'appPlatform': AppInformation.appName});
|
// payload: {'appPlatform': AppInformation.appName});
|
||||||
// Log.print('res: ${res}');
|
// // Log.print('res: ${res}');
|
||||||
if (res != 'failure') {
|
// if (res != 'failure') {
|
||||||
var d = jsonDecode(res);
|
// var d = jsonDecode(res);
|
||||||
isTest = d['message'][0]['isTest'];
|
// isTest = d['message'][0]['isTest'];
|
||||||
// Log.print('isTest: ${isTest}');
|
// // Log.print('isTest: ${isTest}');
|
||||||
box.write(BoxName.isTest, isTest);
|
// box.write(BoxName.isTest, isTest);
|
||||||
|
|
||||||
// Log.print('isTest: ${box.read(BoxName.isTest)}');
|
// // Log.print('isTest: ${box.read(BoxName.isTest)}');
|
||||||
update();
|
// update();
|
||||||
} else {
|
// } else {
|
||||||
isTest = 0;
|
// isTest = 0;
|
||||||
box.write(BoxName.isTest, isTest);
|
// box.write(BoxName.isTest, isTest);
|
||||||
update();
|
// update();
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
updateAppTester(String appPlatform) async {
|
// updateAppTester(String appPlatform) async {
|
||||||
await CRUD().post(
|
// await CRUD().post(
|
||||||
link: AppLink.updateTesterApp, payload: {'appPlatform': appPlatform});
|
// link: AppLink.updateTesterApp, payload: {'appPlatform': appPlatform});
|
||||||
}
|
// }
|
||||||
|
|
||||||
isPhoneVerified() async {
|
isPhoneVerified() async {
|
||||||
var res = await CRUD().post(
|
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;
|
isloading = true;
|
||||||
update();
|
update();
|
||||||
// await SecurityHelper.performSecurityChecks();
|
// await SecurityHelper.performSecurityChecks();
|
||||||
// Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}');
|
// Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}');
|
||||||
// await getJWT();
|
// await getJWT();
|
||||||
var res = await CRUD().get(link: AppLink.loginFromGoogleCaptin, payload: {
|
var res = await CRUD().get(link: AppLink.loginFromGoogleCaptin, payload: {});
|
||||||
// 'email': email ?? 'yet',
|
Log.print('loginDriver: ${res}');
|
||||||
'id': driverID,
|
|
||||||
});
|
|
||||||
Log.print('loginWithGoogleCredential: ${res}');
|
|
||||||
if (res == 'failure') {
|
if (res == 'failure') {
|
||||||
await isPhoneVerified();
|
await isPhoneVerified();
|
||||||
isloading = false; // <--- أضفت هذا أيضاً
|
isloading = false; // <--- أضفت هذا أيضاً
|
||||||
@@ -478,10 +477,7 @@ class LoginDriverController extends GetxController {
|
|||||||
// await SecurityHelper.performSecurityChecks();
|
// await SecurityHelper.performSecurityChecks();
|
||||||
// Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}');
|
// Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}');
|
||||||
|
|
||||||
var res = await CRUD().get(link: AppLink.loginFromGoogleCaptin, payload: {
|
var res = await CRUD().get(link: AppLink.loginFromGoogleCaptin, payload: {});
|
||||||
'email': email ?? 'yet',
|
|
||||||
'id': driverID,
|
|
||||||
});
|
|
||||||
|
|
||||||
// print('res is $res');
|
// print('res is $res');
|
||||||
// if (res == 'failure') {
|
// if (res == 'failure') {
|
||||||
@@ -563,32 +559,34 @@ class LoginDriverController extends GetxController {
|
|||||||
|
|
||||||
loginUsingCredentialsWithoutGoogle(String password, email) async {
|
loginUsingCredentialsWithoutGoogle(String password, email) async {
|
||||||
isloading = true;
|
isloading = true;
|
||||||
isGoogleLogin = true;
|
|
||||||
update();
|
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;
|
try {
|
||||||
update();
|
var fingerPrint = await DeviceHelper.getDeviceFingerprint();
|
||||||
} else {
|
var payload = {
|
||||||
var jsonDecoeded = jsonDecode(res);
|
'email': email,
|
||||||
var d = jsonDecoeded['data'][0];
|
'password': password,
|
||||||
if (jsonDecoeded.isNotEmpty) {
|
'fingerPrint': fingerPrint,
|
||||||
if (jsonDecoeded['status'] == 'success' &&
|
'aud': AppInformation.appName,
|
||||||
d['is_verified'].toString() == '1') {
|
};
|
||||||
|
|
||||||
|
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.emailDriver, (d['email']));
|
||||||
box.write(BoxName.driverID, (d['id']));
|
box.write(BoxName.driverID, (d['id']));
|
||||||
box.write(BoxName.isTest, '1');
|
box.write(BoxName.isTest, '1');
|
||||||
@@ -602,83 +600,24 @@ class LoginDriverController extends GetxController {
|
|||||||
BoxName.nameDriver,
|
BoxName.nameDriver,
|
||||||
'${(d['first_name'])}'
|
'${(d['first_name'])}'
|
||||||
' ${(d['last_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);
|
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(() => HomeCaptain());
|
||||||
// Get.off(() => LoginCaptin());
|
|
||||||
} else {
|
} else {
|
||||||
Get.offAll(() => SmsSignupEgypt());
|
mySnackeBarError('Login failed'.tr);
|
||||||
|
|
||||||
isloading = false;
|
isloading = false;
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
mySnackeBarError('');
|
mySnackeBarError('Server error'.tr);
|
||||||
|
|
||||||
isloading = false;
|
isloading = false;
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
mySnackeBarError('Network error'.tr);
|
||||||
|
isloading = false;
|
||||||
|
update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ class PhoneAuthHelper {
|
|||||||
// Define your server URLs
|
// Define your server URLs
|
||||||
static final String _sendOtpUrl = '${AppLink.server}/auth/otp/request.php';
|
static final String _sendOtpUrl = '${AppLink.server}/auth/otp/request.php';
|
||||||
static final String _verifyOtpUrl = '${AppLink.server}/auth/otp/verify.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
|
// removed formatSyrianPhone
|
||||||
|
|
||||||
/// Sends an OTP to the provided phone number.
|
/// Sends an OTP to the provided phone number.
|
||||||
@@ -89,7 +90,7 @@ class PhoneAuthHelper {
|
|||||||
box.write(BoxName.driverID, driver['id']);
|
box.write(BoxName.driverID, driver['id']);
|
||||||
box.write(BoxName.emailDriver, driver['email']);
|
box.write(BoxName.emailDriver, driver['email']);
|
||||||
|
|
||||||
await Get.find<LoginDriverController>().loginWithGoogleCredential(
|
await Get.find<LoginDriverController>().loginDriver(
|
||||||
driver['id'].toString(), driver['email'].toString());
|
driver['id'].toString(), driver['email'].toString());
|
||||||
} else {
|
} else {
|
||||||
// ✅ رقم الهاتف تم التحقق منه لكن السائق غير مسجل
|
// ✅ رقم الهاتف تم التحقق منه لكن السائق غير مسجل
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ class RegisterCaptainController extends GetxController {
|
|||||||
backgroundColor: AppColor.greenColor);
|
backgroundColor: AppColor.greenColor);
|
||||||
box.write(BoxName.phoneVerified, '1');
|
box.write(BoxName.phoneVerified, '1');
|
||||||
box.write(BoxName.phone, ('+2${phoneController.text}'));
|
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.driverID).toString(),
|
||||||
(box.read(BoxName.emailDriver).toString()),
|
(box.read(BoxName.emailDriver).toString()),
|
||||||
);
|
);
|
||||||
@@ -229,7 +229,7 @@ class RegisterCaptainController extends GetxController {
|
|||||||
mySnackbarSuccess('Phone number is already verified'.tr);
|
mySnackbarSuccess('Phone number is already verified'.tr);
|
||||||
box.write(BoxName.phoneVerified, '1');
|
box.write(BoxName.phoneVerified, '1');
|
||||||
box.write(BoxName.phone, ('+2${phoneController.text}'));
|
box.write(BoxName.phone, ('+2${phoneController.text}'));
|
||||||
Get.put(LoginDriverController()).loginWithGoogleCredential(
|
Get.put(LoginDriverController()).loginDriver(
|
||||||
box.read(BoxName.driverID).toString(),
|
box.read(BoxName.driverID).toString(),
|
||||||
box.read(BoxName.emailDriver).toString(),
|
box.read(BoxName.emailDriver).toString(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -371,7 +371,8 @@ class RegistrationController extends GetxController {
|
|||||||
data.forEach((k, v) => request.fields[k] = v);
|
data.forEach((k, v) => request.fields[k] = v);
|
||||||
|
|
||||||
// المهلة الزمنية 120 ثانية لتناسب الاتصالات الضعيفة
|
// المهلة الزمنية 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);
|
final res = await http.Response.fromStream(streamed);
|
||||||
|
|
||||||
if (res.statusCode != 200) {
|
if (res.statusCode != 200) {
|
||||||
@@ -469,7 +470,8 @@ class RegistrationController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final statusOk = j['status'] == 'success';
|
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 &&
|
if (resp.statusCode == 200 &&
|
||||||
statusOk &&
|
statusOk &&
|
||||||
@@ -512,14 +514,23 @@ class RegistrationController extends GetxController {
|
|||||||
|
|
||||||
final isSyria = box.read(BoxName.countryCode) == 'Syria';
|
final isSyria = box.read(BoxName.countryCode) == 'Syria';
|
||||||
|
|
||||||
if (idFrontUrl == null || idFrontUrl.isEmpty ||
|
if (idFrontUrl == null ||
|
||||||
idBackUrl == null || idBackUrl.isEmpty ||
|
idFrontUrl.isEmpty ||
|
||||||
driverLicenseUrl == null || driverLicenseUrl.isEmpty ||
|
idBackUrl == null ||
|
||||||
(isSyria && (driverLicenseBackUrl == null || driverLicenseBackUrl.isEmpty)) ||
|
idBackUrl.isEmpty ||
|
||||||
profilePicUrl == null || profilePicUrl.isEmpty ||
|
driverLicenseUrl == null ||
|
||||||
carFrontUrl == null || carFrontUrl.isEmpty ||
|
driverLicenseUrl.isEmpty ||
|
||||||
carBackUrl == null || carBackUrl.isEmpty) {
|
(isSyria &&
|
||||||
mySnackbarWarning('Please wait for all documents to finish uploading before registering.'.tr);
|
(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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -626,7 +637,8 @@ class RegistrationController extends GetxController {
|
|||||||
_addField(fields, 'id_front', idFrontUrl);
|
_addField(fields, 'id_front', idFrontUrl);
|
||||||
_addField(fields, 'id_back', idBackUrl);
|
_addField(fields, 'id_back', idBackUrl);
|
||||||
_addField(fields, 'driver_license', driverLicenseUrl);
|
_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, 'profile_picture', profilePicUrl);
|
||||||
_addField(fields, 'criminal_record', criminalRecordUrl);
|
_addField(fields, 'criminal_record', criminalRecordUrl);
|
||||||
_addField(fields, 'car_license_front', carFrontUrl);
|
_addField(fields, 'car_license_front', carFrontUrl);
|
||||||
@@ -671,7 +683,7 @@ class RegistrationController extends GetxController {
|
|||||||
final c = Get.isRegistered<LoginDriverController>()
|
final c = Get.isRegistered<LoginDriverController>()
|
||||||
? Get.find<LoginDriverController>()
|
? Get.find<LoginDriverController>()
|
||||||
: Get.put(LoginDriverController());
|
: Get.put(LoginDriverController());
|
||||||
c.loginWithGoogleCredential(driverID, email);
|
c.loginDriver(driverID, email);
|
||||||
} else {
|
} else {
|
||||||
final msg = (json?['message'] ?? 'Registration failed.').toString();
|
final msg = (json?['message'] ?? 'Registration failed.').toString();
|
||||||
mySnackeBarError(msg);
|
mySnackeBarError(msg);
|
||||||
|
|||||||
@@ -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/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 'package:siro_driver/main.dart';
|
||||||
|
import '../../print.dart';
|
||||||
|
|
||||||
class CountryLogic {
|
class CountryLogic {
|
||||||
/// Formats the phone number based on the country's dialing rules.
|
/// Formats the phone number based on the country's dialing rules.
|
||||||
@@ -91,4 +97,99 @@ class CountryLogic {
|
|||||||
final country = box.read(BoxName.countryCode) ?? 'Syria';
|
final country = box.read(BoxName.countryCode) ?? 'Syria';
|
||||||
return formatPhone(cleanPhone, country);
|
return formatPhone(cleanPhone, country);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// التحقق مما إذا كانت نقطة داخل مضلع (Ray Casting Algorithm)
|
||||||
|
static bool isPointInPolygon(LatLng point, List<LatLng> 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<String?> 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<void> 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<LatLng> getCurrentCityPolygon() {
|
||||||
|
final country = box.read(BoxName.countryCode) ?? 'Jordan';
|
||||||
|
if (country == 'Syria') return CityPolygons.damascusPolygon;
|
||||||
|
if (country == 'Egypt') return CityPolygons.cairoPolygon;
|
||||||
|
return CityPolygons.ammanPolygon;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ class AI extends GetxController {
|
|||||||
// DeviceController().getDeviceSerialNumber();
|
// DeviceController().getDeviceSerialNumber();
|
||||||
box.write(BoxName.phoneVerified, true);
|
box.write(BoxName.phoneVerified, true);
|
||||||
|
|
||||||
Get.find<LoginDriverController>().loginWithGoogleCredential(
|
Get.find<LoginDriverController>().loginDriver(
|
||||||
box.read(BoxName.driverID).toString(),
|
box.read(BoxName.driverID).toString(),
|
||||||
box.read(BoxName.emailDriver).toString(),
|
box.read(BoxName.emailDriver).toString(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class SmsEgyptController extends GetxController {
|
|||||||
('+2${Get.find<RegisterCaptainController>().phoneController.text}'));
|
('+2${Get.find<RegisterCaptainController>().phoneController.text}'));
|
||||||
box.write(BoxName.phoneVerified, '1');
|
box.write(BoxName.phoneVerified, '1');
|
||||||
|
|
||||||
await Get.put(LoginDriverController()).loginWithGoogleCredential(
|
await Get.put(LoginDriverController()).loginDriver(
|
||||||
box.read(BoxName.driverID).toString(),
|
box.read(BoxName.driverID).toString(),
|
||||||
(box.read(BoxName.emailDriver).toString()),
|
(box.read(BoxName.emailDriver).toString()),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ class SplashScreenController extends GetxController
|
|||||||
await initializer.initializeApp();
|
await initializer.initializeApp();
|
||||||
await EncryptionHelper.initialize();
|
await EncryptionHelper.initialize();
|
||||||
|
|
||||||
await loginController.loginWithGoogleCredential(
|
await loginController.loginDriver(
|
||||||
box.read(BoxName.driverID).toString(),
|
box.read(BoxName.driverID).toString(),
|
||||||
box.read(BoxName.emailDriver).toString(),
|
box.read(BoxName.emailDriver).toString(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ class AuthScreen extends StatelessWidget {
|
|||||||
const Text('Login', style: TextStyle(color: Colors.white)),
|
const Text('Login', style: TextStyle(color: Colors.white)),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (testerFormKey.currentState!.validate()) {
|
if (testerFormKey.currentState!.validate()) {
|
||||||
controller.logintest(
|
controller.loginUsingCredentialsWithoutGoogle(
|
||||||
testerPasswordController.text.trim(),
|
testerPasswordController.text.trim(),
|
||||||
testerEmailController.text.trim(),
|
testerEmailController.text.trim(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:intaleq_maps/intaleq_maps.dart';
|
|||||||
import '../../../../constant/api_key.dart';
|
import '../../../../constant/api_key.dart';
|
||||||
import '../../../../controller/functions/location_controller.dart';
|
import '../../../../controller/functions/location_controller.dart';
|
||||||
import '../../../../controller/home/captin/home_captain_controller.dart';
|
import '../../../../controller/home/captin/home_captain_controller.dart';
|
||||||
|
import '../../../../controller/functions/country_logic.dart';
|
||||||
|
|
||||||
class OsmMapView extends StatelessWidget {
|
class OsmMapView extends StatelessWidget {
|
||||||
const OsmMapView({super.key});
|
const OsmMapView({super.key});
|
||||||
@@ -22,27 +23,77 @@ class OsmMapView extends StatelessWidget {
|
|||||||
locationController.myLocation.longitude);
|
locationController.myLocation.longitude);
|
||||||
final double currentHeading = locationController.heading;
|
final double currentHeading = locationController.heading;
|
||||||
|
|
||||||
return IntaleqMap(
|
final cityPolygon = CountryLogic.getCurrentCityPolygon();
|
||||||
apiKey: AK.mapSaasKey,
|
final bool inServiceArea = CountryLogic.isPointInPolygon(currentLocation, cityPolygon);
|
||||||
initialCameraPosition: CameraPosition(
|
|
||||||
target: currentLocation,
|
return Stack(
|
||||||
zoom: 15,
|
children: [
|
||||||
bearing: currentHeading,
|
IntaleqMap(
|
||||||
),
|
apiKey: AK.mapSaasKey,
|
||||||
markers: {
|
initialCameraPosition: CameraPosition(
|
||||||
Marker(
|
target: currentLocation,
|
||||||
markerId: const MarkerId('myLocation'),
|
zoom: 15,
|
||||||
position: currentLocation,
|
bearing: currentHeading,
|
||||||
rotation: currentHeading,
|
),
|
||||||
icon: homeCaptainController.carIcon,
|
markers: {
|
||||||
anchor: const Offset(0.5, 0.5),
|
Marker(
|
||||||
flat: true,
|
markerId: const MarkerId('myLocation'),
|
||||||
zIndex: 2,
|
position: currentLocation,
|
||||||
)
|
rotation: currentHeading,
|
||||||
},
|
icon: homeCaptainController.carIcon,
|
||||||
onMapCreated: (IntaleqMapController controller) {
|
anchor: const Offset(0.5, 0.5),
|
||||||
// You can assign this controller if needed
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -398,6 +398,7 @@ static String get updateTesterApp => "$auth/Tester/updateTesterApp.php";
|
|||||||
static String get signUp => "$auth/signup.php";
|
static String get signUp => "$auth/signup.php";
|
||||||
static String get sendVerifyEmail => "$auth/sendVerifyEmail.php";
|
static String get sendVerifyEmail => "$auth/sendVerifyEmail.php";
|
||||||
static String get loginFromGooglePassenger => "$auth/loginFromGooglePassenger.php";
|
static String get loginFromGooglePassenger => "$auth/loginFromGooglePassenger.php";
|
||||||
|
static String get loginUsingCredentialsWithoutGooglePassenger => "$auth/loginUsingCredentialsWithoutGooglePassenger.php";
|
||||||
static String get checkPhoneNumberISVerfiedPassenger =>
|
static String get checkPhoneNumberISVerfiedPassenger =>
|
||||||
"$auth/checkPhoneNumberISVerfiedPassenger.php";
|
"$auth/checkPhoneNumberISVerfiedPassenger.php";
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import 'package:location/location.dart';
|
|||||||
import '../../env/env.dart';
|
import '../../env/env.dart';
|
||||||
import '../../print.dart';
|
import '../../print.dart';
|
||||||
import '../../views/auth/otp_token_page.dart';
|
import '../../views/auth/otp_token_page.dart';
|
||||||
|
import '../functions/country_logic.dart';
|
||||||
import '../functions/encrypt_decrypt.dart';
|
import '../functions/encrypt_decrypt.dart';
|
||||||
import '../functions/package_info.dart';
|
import '../functions/package_info.dart';
|
||||||
|
|
||||||
@@ -48,7 +49,8 @@ class LoginController extends GetxController {
|
|||||||
@override
|
@override
|
||||||
void onInit() async {
|
void onInit() async {
|
||||||
// Log.print('box.read(BoxName.isTest): ${box.read(BoxName.isTest)}');
|
// Log.print('box.read(BoxName.isTest): ${box.read(BoxName.isTest)}');
|
||||||
box.write(BoxName.countryCode, 'Syria');
|
// كشف الدولة تلقائياً عبر الموقع الجغرافي
|
||||||
|
await CountryLogic.initializeCountry();
|
||||||
FirebaseMessagesController().getToken();
|
FirebaseMessagesController().getToken();
|
||||||
super.onInit();
|
super.onInit();
|
||||||
}
|
}
|
||||||
@@ -271,8 +273,6 @@ class LoginController extends GetxController {
|
|||||||
final res = await CRUD().get(
|
final res = await CRUD().get(
|
||||||
link: AppLink.loginFromGooglePassenger,
|
link: AppLink.loginFromGooglePassenger,
|
||||||
payload: {
|
payload: {
|
||||||
'email': email,
|
|
||||||
'id': passengerID, // استخدم المعامل مباشرة
|
|
||||||
'platform': Platform.isAndroid ? 'android' : 'ios',
|
'platform': Platform.isAndroid ? 'android' : 'ios',
|
||||||
'appName': AppInformation.appName,
|
'appName': AppInformation.appName,
|
||||||
},
|
},
|
||||||
@@ -385,7 +385,7 @@ class LoginController extends GetxController {
|
|||||||
"inviteCode": invite,
|
"inviteCode": invite,
|
||||||
"passengerID": passengerID,
|
"passengerID": passengerID,
|
||||||
});
|
});
|
||||||
|
|
||||||
// سجل الدعوة أيضاً في النظام الموحد الجديد
|
// سجل الدعوة أيضاً في النظام الموحد الجديد
|
||||||
await CRUD().post(link: AppLink.addUnifiedInvite, payload: {
|
await CRUD().post(link: AppLink.addUnifiedInvite, payload: {
|
||||||
"inviter_code": invite,
|
"inviter_code": invite,
|
||||||
@@ -445,46 +445,48 @@ class LoginController extends GetxController {
|
|||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
|
|
||||||
void login() async {
|
void loginTester() async {
|
||||||
isloading = true;
|
isloading = true;
|
||||||
update();
|
update();
|
||||||
var res =
|
|
||||||
await CRUD().get(link: AppLink.loginFromGooglePassenger, payload: {
|
try {
|
||||||
'email': (emailController.text),
|
var fingerPrint = await DeviceHelper.getDeviceFingerprint();
|
||||||
'id': passwordController.text,
|
var payload = {
|
||||||
"platform": Platform.isAndroid ? 'android' : 'ios',
|
'email': emailController.text.trim(),
|
||||||
"appName": AppInformation.appName,
|
'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.isVerified, '1');
|
||||||
box.write(BoxName.email, jsonDecoeded['data'][0]['email']);
|
box.write(BoxName.email, d['email']);
|
||||||
box.write(BoxName.name, jsonDecoeded['data'][0]['first_name']);
|
box.write(BoxName.name, d['first_name']);
|
||||||
box.write(BoxName.phone, jsonDecoeded['data'][0]['phone']);
|
box.write(BoxName.phone, d['phone']);
|
||||||
box.write(BoxName.passengerID, passwordController.text);
|
box.write(BoxName.passengerID, d['id']);
|
||||||
// var token = await CRUD().get(link: AppLink.getTokens, payload: {
|
box.write(BoxName.isTest, '1');
|
||||||
// 'passengerID': box.read(BoxName.passengerID).toString()
|
|
||||||
// });
|
await storage.write(key: BoxName.fingerPrint, value: fingerPrint);
|
||||||
// await updateAppTester(AppInformation.appName);
|
|
||||||
|
|
||||||
Get.offAll(() => const MapPagePassenger());
|
Get.offAll(() => const MapPagePassenger());
|
||||||
} else {
|
} else {
|
||||||
// Get.offAll(() => SmsSignupEgypt());
|
Get.offAll(() => LoginPage());
|
||||||
// Get.snackbar(jsonDecoeded['status'], jsonDecoeded['data'],
|
|
||||||
// backgroundColor: Colors.redAccent);
|
|
||||||
isloading = false;
|
isloading = false;
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
@@ -492,6 +494,10 @@ class LoginController extends GetxController {
|
|||||||
isloading = false;
|
isloading = false;
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Log.print("Tester Login Error: $e");
|
||||||
|
isloading = false;
|
||||||
|
update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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/box_name.dart';
|
||||||
|
import 'package:siro_rider/constant/country_polygons.dart';
|
||||||
import 'package:siro_rider/main.dart';
|
import 'package:siro_rider/main.dart';
|
||||||
|
import '../../print.dart';
|
||||||
|
|
||||||
class CountryLogic {
|
class CountryLogic {
|
||||||
/// Formats the phone number based on the country's dialing rules.
|
/// Formats the phone number based on the country's dialing rules.
|
||||||
@@ -88,7 +92,77 @@ class CountryLogic {
|
|||||||
return formatPhone(cleanPhone, 'Jordan');
|
return formatPhone(cleanPhone, 'Jordan');
|
||||||
}
|
}
|
||||||
|
|
||||||
final country = box.read(BoxName.countryCode) ?? 'Syria';
|
final country = box.read(BoxName.countryCode) ?? 'Jordan';
|
||||||
return formatPhone(cleanPhone, country);
|
return formatPhone(cleanPhone, country);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// التحقق مما إذا كانت نقطة داخل مضلع (Ray Casting Algorithm)
|
||||||
|
static bool _isPointInPolygon(LatLng point, List<LatLng> 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<String?> 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<void> 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');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,14 +75,39 @@ class CRUD {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
// دالة مساعدة خاصة: يجيب البصمة المشفرة من GetStorage
|
// دالة مساعدة خاصة: تجيب البصمة المشفرة من GetStorage
|
||||||
// هي نفس القيمة المرسلة في login وعُملها hash في JWT payload
|
|
||||||
// السيرفر يعمل: sha256(X-Device-FP + FP_PEPPER) == JWT.fingerPrint
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
String _getFpHeader() {
|
String _getFpHeader() {
|
||||||
return box.read(BoxName.deviceFpEncrypted)?.toString() ?? '';
|
return box.read(BoxName.deviceFpEncrypted)?.toString() ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
// دالة مساعدة خاصة: تقرأ JWT من FlutterSecureStorage (آمن)
|
||||||
|
// بدلاً من GetStorage (غير مشفر)
|
||||||
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
Future<String> _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.
|
/// Centralized private method to handle all API requests.
|
||||||
/// Includes retry logic, network checking, and standardized error handling.
|
/// Includes retry logic, network checking, and standardized error handling.
|
||||||
Future<dynamic> _makeRequest({
|
Future<dynamic> _makeRequest({
|
||||||
@@ -169,7 +194,7 @@ class CRUD {
|
|||||||
required String link,
|
required String link,
|
||||||
Map<String, dynamic>? payload,
|
Map<String, dynamic>? payload,
|
||||||
}) async {
|
}) async {
|
||||||
String token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
|
final token = await _getJwt();
|
||||||
|
|
||||||
final headers = {
|
final headers = {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
@@ -193,14 +218,14 @@ class CRUD {
|
|||||||
required String link,
|
required String link,
|
||||||
Map<String, dynamic>? payload,
|
Map<String, dynamic>? payload,
|
||||||
}) async {
|
}) async {
|
||||||
|
final token = await _getJwt();
|
||||||
var url = Uri.parse(link);
|
var url = Uri.parse(link);
|
||||||
var response = await http.post(
|
var response = await http.post(
|
||||||
url,
|
url,
|
||||||
body: payload,
|
body: payload,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
'Authorization':
|
'Authorization': 'Bearer $token',
|
||||||
'Bearer ${r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]}',
|
|
||||||
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
|
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -548,7 +573,6 @@ class CRUD {
|
|||||||
return response.statusCode;
|
return response.statusCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Future<dynamic> postPayMob({
|
Future<dynamic> postPayMob({
|
||||||
required String link,
|
required String link,
|
||||||
Map<String, dynamic>? payload,
|
Map<String, dynamic>? payload,
|
||||||
|
|||||||
@@ -4,12 +4,9 @@ import 'package:siro_rider/constant/box_name.dart';
|
|||||||
import 'package:siro_rider/constant/info.dart';
|
import 'package:siro_rider/constant/info.dart';
|
||||||
import 'package:siro_rider/controller/auth/login_controller.dart';
|
import 'package:siro_rider/controller/auth/login_controller.dart';
|
||||||
import 'package:jwt_decoder/jwt_decoder.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 '../../constant/links.dart';
|
||||||
import '../../main.dart';
|
import '../../main.dart';
|
||||||
import '../../print.dart';
|
|
||||||
import 'crud.dart';
|
import 'crud.dart';
|
||||||
import 'encrypt_decrypt.dart';
|
import 'encrypt_decrypt.dart';
|
||||||
|
|
||||||
@@ -24,23 +21,25 @@ class SecureStorage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const List<String> keysToFetch = [
|
|
||||||
'serverPHP',
|
|
||||||
// 'seferAlexandriaServer',
|
|
||||||
// 'seferPaymentServer',
|
|
||||||
// 'seferCairoServer',
|
|
||||||
// 'seferGizaServer',
|
|
||||||
];
|
|
||||||
|
|
||||||
class AppInitializer {
|
class AppInitializer {
|
||||||
List<Map<String, dynamic>> links = [];
|
List<Map<String, dynamic>> links = [];
|
||||||
|
|
||||||
|
/// تقرأ JWT من FlutterSecureStorage أولاً، مع fallback إلى GetStorage
|
||||||
|
Future<String?> _readJwt() async {
|
||||||
|
try {
|
||||||
|
final secureJwt = await storage.read(key: 'jwt');
|
||||||
|
if (secureJwt != null && secureJwt.isNotEmpty) return secureJwt;
|
||||||
|
} catch (_) {}
|
||||||
|
return box.read(BoxName.jwt);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> initializeApp() async {
|
Future<void> initializeApp() async {
|
||||||
if (box.read(BoxName.jwt) == null) {
|
final jwt = await _readJwt();
|
||||||
|
if (jwt == null) {
|
||||||
await LoginController().getJWT();
|
await LoginController().getJWT();
|
||||||
} else {
|
} else {
|
||||||
bool isTokenExpired = JwtDecoder.isExpired(
|
bool isTokenExpired =
|
||||||
r(box.read(BoxName.jwt)).toString().split(AppInformation.addd)[0]);
|
JwtDecoder.isExpired(r(jwt).toString().split(AppInformation.addd)[0]);
|
||||||
|
|
||||||
if (isTokenExpired) {
|
if (isTokenExpired) {
|
||||||
await LoginController().getJWT();
|
await LoginController().getJWT();
|
||||||
|
|||||||
@@ -419,7 +419,7 @@ class LoginPage extends StatelessWidget {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (controller.formKey.currentState!
|
if (controller.formKey.currentState!
|
||||||
.validate()) {
|
.validate()) {
|
||||||
controller.login();
|
controller.loginTester();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ class AuthScreen extends StatelessWidget {
|
|||||||
testerEmailController.text;
|
testerEmailController.text;
|
||||||
controller.passwordController.text =
|
controller.passwordController.text =
|
||||||
testerPasswordController.text;
|
testerPasswordController.text;
|
||||||
controller.login();
|
controller.loginTester();
|
||||||
Navigator.of(dialogContext).pop();
|
Navigator.of(dialogContext).pop();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user