diff --git a/auth_flow_admin_staff.md b/auth_flow_admin_staff.md
new file mode 100644
index 0000000..3dc4250
--- /dev/null
+++ b/auth_flow_admin_staff.md
@@ -0,0 +1,676 @@
+
+
+# سير عمل تسجيل الدخول في نظام Siro
+
+## توثيق شامل لتدفق المصادقة للمشرفين (Super Admin / Admin) وموظفي خدمة العملاء (Service Staff)
+
+---
+
+## 📋 فهرس المحتويات
+
+1. [نظرة عامة على النظام](#نظرة-عامة-على-النظام)
+2. [المكونات الرئيسية](#المكونات-الرئيسية)
+3. [سير عمل تسجيل حساب مشرف جديد](#سير-عمل-تسجيل-حساب-مشرف-جديد)
+4. [سير عمل تسجيل الدخول (المشرف - siro_admin)](#سير-عمل-تسجيل-الدخول-المشرف---siro_admin)
+5. [سير عمل تسجيل الدخول (موظف الخدمة - siro_service)](#سير-عمل-تسجيل-الدخول-موظف-الخدمة---siro_service)
+6. [سير عمل إضافة الموظفين من قبل المشرف العام](#سير-عمل-إضافة-الموظفين-من-قبل-المشرف-العام)
+7. [سير عمل تفعيل الحسابات المعلقة](#سير-عمل-تفعيل-الحسابات-المعلقة)
+8. [مقارنة بين تدفق siro_admin و siro_service](#مقارنة-بين-تدفق-siro_admin-و-siro_service)
+9. [الجوانب الأمنية (Security)](#الجوانب-الأمنية-security)
+10. [قائمة الإصلاحات الأمنية المُنفَّذة](#قائمة-الإصلاحات-الأمنية-المنفذة)
+11. [الإجراءات الموصى بها للتحسين (Recommendations)](#الإجراءات-الموصى-بها-للتحسين-recommendations)
+
+---
+
+## نظرة عامة على النظام
+
+نظام Siro يستخدم بنية متعددة التطبيقات (Multi-App Architecture):
+
+| التطبيق | الدور | ملفات المصادقة |
+|---------|-------|-----------------|
+| **siro_admin** | المشرفون (Admin / Super Admin) | `siro_admin/lib/views/auth/login_page.dart`, `otp_helper.dart` |
+| **siro_service** | موظفو خدمة العملاء | `siro_service/lib/controller/login_controller.dart` |
+| **Backend (PHP)** | الخادم المركزي | `backend/Admin/auth/login.php`, `backend/serviceapp/login.php` |
+
+المبدأ الأساسي: **المصادقة متعددة العوامل (MFA)** تعتمد على:
+
+1. **بصمة الجهاز (Fingerprint)** — يتم إنشاؤها في التطبيق وإرسالها مع كل طلب
+2. **كلمة المرور** — مشفرة باستخدام `password_hash`
+3. **OTP عبر WhatsApp** — للتحقق الإضافي (للمشرفين حصراً)
+4. **JWT** — التوكن النهائي للوصول
+
+---
+
+## المكونات الرئيسية
+
+### تطبيق siro_admin (المشرفون)
+
+| الملف | المسار | الوظيفة |
+|-------|--------|---------|
+| `login_page.dart` | `siro_admin/lib/views/auth/` | واجهة تسجيل الدخول (كلمة مرور + هاتف اختياري) |
+| `register_page.dart` | `siro_admin/lib/views/auth/` | واجهة طلب حساب مشرف جديد |
+| `otp_helper.dart` | `siro_admin/lib/controller/auth/` | منطق OTP وجدولة إعادة الدخول (Auto Login) |
+| `register_controller.dart` | `siro_admin/lib/controller/auth/` | منطق التسجيل |
+
+### تطبيق siro_service (خدمة العملاء)
+
+| الملف | المسار | الوظيفة |
+|-------|--------|---------|
+| `login_controller.dart` | `siro_service/lib/controller/` | منطق تسجيل الدخول مع OTP |
+| (لا يوجد register) | | التسجيل يتم عبر `backend/serviceapp/register.php` أو عبر `add.php` |
+
+### الباك إند (PHP)
+
+| الملف | المسار | الوظيفة |
+|-------|--------|---------|
+| `login.php` | `backend/Admin/auth/` | تسجيل دخول المشرف (الخطوة الأولى) |
+| `verify_login.php` | `backend/Admin/auth/` | التحقق من OTP للمشرف (الخطوة الثانية) |
+| `register.php` | `backend/Admin/auth/` | تسجيل مشرف جديد |
+| `login.php` | `backend/serviceapp/` | تسجيل دخول موظف الخدمة |
+| `register.php` | `backend/serviceapp/` | تسجيل موظف خدمة جديد |
+| `add.php` | `backend/Admin/Staff/` | إضافة موظف/مشرف من قبل المشرف العام |
+| `pending.php` | `backend/Admin/Staff/` | جلب الحسابات المعلقة |
+| `activate.php` | `backend/Admin/Staff/` | تفعيل الحسابات المعلقة |
+| `jwtService.php` | `backend/Admin/jwtService.php` | إصدار JWT لخدمة العملاء (قديم) |
+| `JwtService.php` | `backend/core/Auth/` | خدمة JWT الأساسية مع Authentication |
+
+---
+
+## سير عمل تسجيل حساب مشرف جديد
+
+### 📍 يبدأ من تطبيق siro_admin ← `register_page.dart`
+
+```
+[المستخدم] [التطبيق (Flutter)] [الباك إند (PHP)] [قاعدة البيانات]
+ | | | |
+ |── يدخل الاسم، الهاتف، | | |
+ | كلمة المرور | | |
+ |─────────────────────────────>| | |
+ | | | |
+ | |── قراءة بصمة الجهاز (fingerprint) | |
+ | | من `box.read('fingerprint')` | |
+ | | | |
+ | |── POST `/Admin/auth/register.php` | |
+ | | مع: name, phone, password, fingerprint | |
+ | |──────────────────────────────────────────>| |
+ | | | |
+ | | |── التحقق من القائمة البيضاء |
+ | | | `AUTHORIZED_ADMIN_PHONES` |
+ | | | في متغيرات البيئة (.env) |
+ | | | |
+ | | |── التحقق من التكرار: |
+ | | | phone OR fingerprint_hash |
+ | | | |
+ | | |── تشفير البيانات: |
+ | | | • name ← encryptData() |
+ | | | • phone ← encryptData() |
+ | | | • fingerprint ← encrypt() |
+ | | | • fingerprint_hash = |
+ | | | SHA-256(fingerprint) |
+ | | | • password ← password_hash |
+ | | | |
+ | | |── توليد UUID آمن: |
+ | | | bin2hex(random_bytes(16)) |
+ | | | |
+ | | |── INSERT INTO adminUser |
+ | | | status = 'pending' |
+ | | | role = 'admin' |
+ | | |─────────────────────────────>| (id, fingerprint, fingerprint_hash,
+ | | | | name, phone, password, role,
+ | | | | status='pending', created_at)
+ | | | |
+ | |<── { status: "pending", message: "..." } | |
+ | | | |
+ |<── رسالة "تم تقديم الطلب" | | |
+```
+
+### ملاحظات أمنية حول التسجيل:
+
+- ✅ القائمة البيضاء (`AUTHORIZED_ADMIN_PHONES`) تمنع أي شخص من التسجيل دون إذن مسبق
+- ✅ الاسم والهاتف والبصمة مشفرة في قاعدة البيانات
+- ✅ بصمة الجهاز محولة إلى SHA-256 Hash للبحث السريع
+- ✅ **`bin2hex(random_bytes(16))`** لتوليد UUID آمن — تم إصلاحه من `rand()`
+- 🔴 الحساب ينشأ بحالة `pending` ولا يمكنه الدخول حتى يتم تفعيله يدوياً
+
+---
+
+## سير عمل تسجيل الدخول (المشرف - siro_admin)
+
+### 📍 يبدأ من `login_page.dart` ← `OtpHelper.loginWithPassword()`
+
+#### الخطوة الأولى: إرسال طلب الدخول
+
+```
+[المستخدم] [otp_helper.dart] [login.php (Backend)] [Redis / DB]
+ | | | |
+ |── يدخل كلمة المرور | | |
+ | (ورقم الهاتف لأول | | |
+ | مرة) | | |
+ |──────────────────────>| | |
+ | | | |
+ | |── POST /Admin/auth/login.php | |
+ | | payload: fingerprint, | |
+ | | password, phone (اختياري), | |
+ | | aud (اختياري), is_renewal | |
+ | |──────────────────────────────────>| |
+ | | | |
+ | | |── Rate Limiting: |
+ | | | 5 محاولات/دقيقة لكل IP |
+ | | | |
+ | | |── البحث بـ fingerprint_hash |
+ | | | (SHA-256 للبصمة) |
+ | | | |
+ | | [إذا لم يوجد بالبصمة والهاتف موجود] |
+ | | |── البحث بالهاتف المشفر |
+ | | | (لأول مرة أو جهاز جديد) |
+ | | | |
+ | | |── التحقق من status: |
+ | | | • pending → رفض مع رسالة |
+ | | | • suspended → رفض |
+ | | | • rejected → رفض |
+ | | | • active → متابعة |
+ | | | |
+ | | |── التحقق من كلمة المرور: |
+ | | | password_verify(password) |
+ | | | |
+```
+
+#### التفرع حسب `is_renewal`:
+
+```
+ |
+ ┌───────────────┴───────────────┐
+ | |
+ is_renewal=1 is_renewal=0 أو missing
+ (إعادة دخول تلقائي) (تسجيل دخول يدوي)
+ | |
+ | ├── توليد OTP عشوائي
+ | | (100000-999999) ← 6 أرقام
+ | |
+ | ├── فك تشفير رقم الهاتف
+ | | ← إرسال OTP عبر WhatsApp
+ | |
+ | ├── حفظ OTP مشفراً:
+ | | `token_verification_admin`
+ | | (phone مشفر، token مشفر،
+ | | expiration 10 دقائق)
+ | |
+ | └── رد: { status: "otp_required",
+ | phone: masked }
+ |
+ ├── إلغاء التوكن القديم من Redis
+ | (Token Revocation)
+ |
+ ├── توليد JWT جديد:
+ | generateAccessToken(id, role, aud, fingerprint)
+ |
+ └── رد مباشر: { jwt, admin, expires_in }
+```
+
+### الخطوة الثانية (عند طلب OTP): التحقق ← `verify_login.php`
+
+```
+[otp_helper.dart] [verify_login.php] [DB / Redis]
+ | | |
+ |── POST /Admin/auth/ | |
+ | verify_login.php | |
+ | payload: otp, | |
+ | fingerprint, phone | |
+ |────────────────────────────>| |
+ | | |
+ | |── Rate Limiting: |
+ | | 3 محاولات OTP/5 دقائق لكل IP |
+ | | |
+ | |── البحث بالـ fingerprint_hash |
+ | | من جدول adminUser |
+ | | |
+ | |── تشفير OTP المدخل |
+ | | encryptData(otp) |
+ | | |
+ | |── البحث في token_verification: |
+ | | phone_number = encryptedPhone |
+ | | AND token = encryptedOtp |
+ | | AND expiration >= NOW() |
+ | | |
+ | [غير صالح أو منتهي] | |
+ |<── "رمز التحقق غير صالح" | |
+ | | |
+ | [صالح] | |
+ | |── حذف OTP (استخدام لمرة واحدة) |
+ | | DELETE FROM token_verification |
+ | | |
+ | |── إلغاء التوكن القديم من Redis |
+ | | |
+ | |── توليد JWT جديد: |
+ | | generateAccessToken(id, role, |
+ | | aud, fingerprint) |
+ | | |
+ |<── { jwt, admin, expires_in }| |
+ | | |
+ |── حفظ البيانات محلياً: | |
+ | • JWT ← box.write(jwt) | |
+ | • admin_id ← box.write | |
+ | • admin_role ← box.write | |
+ | • phoneVerified ← true | |
+ | • admin_password ← حفظ | |
+ | | |
+ |── Get.offAll(AdminHomePage())| |
+```
+
+### مخطط الحالة الكامل للمشرف (State Machine):
+
+```
+ ┌───────────┐
+ │ Pending │ ← بعد التسجيل، بحاجة تفعيل
+ └─────┬─────┘
+ │ Activate.php (يتطلب JWT مع role=admin)
+ ▼
+ ┌─────────────────┐
+ │ Active │ ← يمكنه تسجيل الدخول
+ └────────┬────────┘
+ │
+ ┌─────────┴──────────┐
+ │ │
+ ▼ ▼
+ ┌───────────┐ ┌───────────┐
+ │ Suspended │ │ Rejected │
+ └───────────┘ └───────────┘
+```
+
+---
+
+## سير عمل تسجيل الدخول (موظف الخدمة - siro_service)
+
+### 📍 يبدأ من `login_controller.dart` ← `login()`
+
+```
+[LoginController] [serviceapp/login.php] [DB / Redis]
+ | | |
+ |── قراءة البصمة من | |
+ | box.read(fingerprint) | |
+ | | |
+ |── POST /serviceapp/login.php | |
+ | payload: fingerprint, | |
+ | password, email, | |
+ | aud = "service" | |
+ |────────────────────────────>| |
+ | | |
+ | |── Rate Limiting: |
+ | | 5 محاولات/دقيقة لكل IP |
+ | | |
+ | |── البحث بـ fingerprint_hash |
+ | | في `users` WHERE user_type |
+ | | = 'service' |
+ | | |
+ | [إذا لم يوجد, والإيميل موجود] |
+ | |── البحث بالإيميل المشفر |
+ | | |
+ | |── التحقق من status: |
+ | | • pending → "قيد المراجعة" |
+ | | • suspended → "معلق" |
+ | | • approved → متابعة |
+ | | |
+ | |── التحقق من كلمة المرور |
+ | | |
+ | |── فك تشفير البيانات للعرض: |
+ | | first_name, last_name, |
+ | | email, phone |
+ | | |
+ | |── إدارة التوكنات: |
+ | | 1. البحث عن توكن موجود في |
+ | | Redis (active_token) |
+ | | 2. إذا وجد وصالح ← استخدامه |
+ | | 3. إذا لم يوجد ← إلغاء القديم |
+ | | وتوليد جديد |
+ | | |
+ | |── توليد HMAC Key: |
+ | | hmac = hash_hmac(sha256, |
+ | | userId, SECRET_KEY_HMAC) |
+ | | |
+ |<── { message, data, jwt, | |
+ | hmac, expires_in } | |
+ | | |
+ |── إرسال OTP: | |
+ | POST /auth/otp/request.php | |
+ | payload: receiver=phone, | |
+ | user_type='service' | |
+ | | |
+ |── عرض Dialog OTP | |
+ | | |
+ |── التحقق من OTP: | |
+ | POST /auth/otp/verify.php | |
+ | payload: phone_number, | |
+ | token_code, user_type | |
+ | | |
+ |── حفظ JWT + HMAC محلياً | |
+ | | |
+ |── Get.offAll(Main()) | |
+```
+
+---
+
+## سير عمل إضافة الموظفين من قبل المشرف العام
+
+### 📍 `backend/Admin/Staff/add.php`
+
+يستخدم هذا الملف لإضافة موظفين جدد من قبل المشرف العام (Super Admin أو Admin فقط).
+
+```
+ ┌─────────────────────────────────────┐
+ │ add.php │
+ │ │
+ │ ✅ JWT Authentication: │
+ │ $jwtService = new JwtService │
+ │ $auth = $jwtService->authenticate│
+ │ $authRole = $auth->role │
+ │ if role ≠ super_admin || admin → │
+ │ رفض الطلب │
+ └──────────────────┬──────────────────┘
+ │
+ ──────────────┼──────────────
+ role='admin' │ role='service'
+ │
+ ┌─────────────────────────┴──────────────────────────┐
+ ▼ ▼
+ ┌────────────────┐ ┌──────────────────┐
+ │ adminUser │ │ users │
+ │ table │ │ table │
+ ├────────────────┤ ├──────────────────┤
+ │ id (bin2hex) │ │ id (bin2hex) │
+ │ fingerprint │ │ fingerprint │
+ │ fingerprint_ │ │ fingerprint_hash │
+ │ hash │ │ phone (مشفر) │
+ │ name (مشفر) │ │ email (مشفر) │
+ │ phone (مشفر) │ │ gender │
+ │ password │ │ password │
+ │ role 'admin' │ │ birthdate │
+ │ created_at │ │ user_type │
+ └────────────────┘ │ 'service' │
+ │ first_name (مشفر) │
+ │ last_name │
+ │ site │
+ │ created_at │
+ └──────────────────┘
+```
+
+### 🔐 آلية التحقق في add.php (بعد الإصلاح):
+
+```php
+$jwtService = new JwtService($redis);
+$auth = $jwtService->authenticate(); // يقرأ Bearer token من Authorization header
+$authRole = $auth->role ?? ''; // يستخرج الدور من JWT payload
+if ($authRole !== 'super_admin' && $authRole !== 'admin') {
+ jsonError("غير مصرح لك. فقط المشرفون يمكنهم إضافة موظفين.");
+ exit;
+}
+```
+
+### نقاط مهمة في add.php:
+
+- ✅ `bin2hex(random_bytes(16))` لتوليد UUID آمن
+- ✅ البيانات الحساسة مشفرة قبل الحفظ
+- ✅ `fingerprint` اختياري — يمكن إضافة موظف بدون بصمة مسبقة
+- ✅ **التحقق من JWT Authentication** مطلوب قبل الإضافة
+
+---
+
+## سير عمل تفعيل الحسابات المعلقة
+
+### 📍 `pending.php` → `activate.php`
+
+#### استعراض الحسابات المعلقة:
+
+```
+[pending.php] [قاعدة البيانات]
+ │ │
+ │── SELECT FROM adminUser │
+ │ WHERE status='pending' │
+ │ │
+ │── SELECT FROM users │
+ │ WHERE status='pending' │
+ │ AND user_type='service' │
+ │ │
+ │── فك تشفير الأسماء والأرقام │
+ │ │
+ │── دمج النتائج (array_merge) │
+ │ │
+ │<── { data: [all_pending] } │
+```
+
+#### تفعيل حساب:
+
+```
+[activate.php] [قاعدة البيانات]
+ │ │
+ │── التحقق من JWT: │
+ │ $jwtService = new JwtService │
+ │ $auth = $jwtService->authenticate│
+ │ $authRole = $auth->role │
+ │ if ≠ super_admin && ≠ admin → │
+ │ رفض │
+ │ │
+ │── POST مع: user_id, type │
+ │ │
+ │ type='admin': │
+ │ UPDATE adminUser │
+ │ SET status='active' │
+ │ WHERE id=user_id │
+ │ AND status='pending' │
+ │ │
+ │ type='service': │
+ │ UPDATE users │
+ │ SET status='approved' │
+ │ WHERE id=user_id │
+ │ AND status='pending' │
+ │ AND user_type='service' │
+ │ │
+ │<── "تم التفعيل بنجاح" │
+```
+
+### 🔐 آلية التحقق في activate.php (بعد الإصلاح):
+
+```php
+$jwtService = new JwtService($redis);
+$auth = $jwtService->authenticate();
+$authRole = $auth->role ?? '';
+if ($authRole !== 'super_admin' && $authRole !== 'admin') {
+ jsonError("غير مصرح لك. فقط المشرف العام يمكنه تفعيل الحسابات.");
+ exit;
+}
+```
+
+---
+
+## مقارنة بين تدفق siro_admin و siro_service
+
+| الخاصية | siro_admin (المشرف) | siro_service (خدمة العملاء) |
+|---------|---------------------|-----------------------------|
+| **جدول البيانات** | `adminUser` | `users` |
+| **مفتاح البحث** | بصمة الجهاز ← الهاتف | بصمة الجهاز ← الإيميل |
+| **آلية OTP** | OTP عبر WhatsApp كخطوة قبل JWT | JWT يصدر أولاً ثم OTP كخطوة تأكيد |
+| **Auto Login** | Yes (is_renewal=1) + JWT غير منتهي | Yes (كلمة مرور مخزنة) |
+| **إلغاء التوكن القديم** | قبل إصدار الجديد (Redis) | قبل إصدار الجديد (Redis) |
+| **عدد أرقام OTP** | 6 أرقام (مُحسَّن) | لا يوجد OTP مدمج بالـ login |
+| **HMAC Key** | غير مستخدم | يستخدم للتوافق مع CRUD |
+| **نوع التسجيل** | تسجيل ذاتي + قائمة بيضاء | إضافة يدوية أو تسجيل ذاتي |
+| **الحالة عند الإنشاء** | `pending` | `pending` / `active` |
+
+---
+
+## الجوانب الأمنية (Security)
+
+### ✅ نقاط القوة
+
+1. **تشفير البيانات الحساسة** — جميع PII (الاسم، الهاتف، الإيميل، البصمة) مشفرة في قاعدة البيانات بواسطة `encryptData()`
+2. **بصمة الجهاز (Fingerprint)** — ربط الحساب بجهاز معين يمنع الوصول من أجهزة غير معروفة
+3. **إلغاء التوكن (Token Revocation)** — التوكن القديم يُلغى قبل إصدار الجديد عبر Redis
+4. **OTP لمدة محدودة** — صلاحية 10 دقائق فقط للرمز
+5. **استخدام لمرة واحدة** — OTP يُحذف بعد التحقق
+6. **القائمة البيضاء** — التسجيل الذاتي مقيد بأرقام مصرح بها من `.env`
+7. **Hash للبصمة** — `SHA-256` للبحث السريع دون تخزين البصمة كما هي
+8. **قناع رقم الهاتف** — في الاستجابة، يظهر فقط `07XX***XXX`
+9. **UUID آمن** — `bin2hex(random_bytes(16))` بدلاً من `rand()` (تم الإصلاح)
+10. **JWT Authentication** — لملفي add.php و activate.php (تم الإصلاح)
+11. **Rate Limiting** — على login و OTP (تم الإضافة)
+
+### ✅ الإصلاحات الأمنية المُنفَّذة
+
+| # | الثغرة | الملف | الحالة |
+|---|--------|-------|--------|
+| 1 | `rand()` لتوليد ID (ضعيف) | `Admin/auth/register.php` | ✅ تم الاستبدال بـ `bin2hex(random_bytes(16))` |
+| 2 | التحقق من الصلاحيات معلق (Commented Out) | `Admin/Staff/add.php` | ✅ تم التفعيل عبر `JwtService::authenticate()` |
+| 3 | لا يوجد تحقق من صلاحية المشرف العام | `Admin/Staff/activate.php` | ✅ تم إضافة JWT + التحقق من role |
+| 4 | لا يوجد Rate Limiting على login | `Admin/auth/login.php` | ✅ تم الإضافة (5 محاولات/دقيقة) |
+| 5 | لا يوجد Rate Limiting على login | `serviceapp/login.php` | ✅ تم الإضافة (5 محاولات/دقيقة) |
+| 6 | لا يوجد Rate Limiting على OTP | `Admin/auth/verify_login.php` | ✅ تم الإضافة (3 محاولات/5 دقائق) |
+| 7 | OTP 5 أرقام (ضعيف) | `Admin/auth/login.php` | ✅ تم الرفع إلى 6 أرقام (100000-999999) |
+
+### ❌ الثغرات المتبقية (لم تُعالَج بعد)
+
+| الثغرة | الموقع | التأثير |
+|--------|--------|---------|
+| **كلمات مرور plain text (قديمة)** | `Admin/jwtService.php` (السطر 49) | دعم كلمات المرور غير المشفرة للتوافق القديم |
+| **JWT Service مكرر** | `Admin/jwtService.php` و `core/Auth/JwtService.php` | يوجد ملفان باسم JwtService بوظائف مختلفة |
+| **Auto Login بدون OTP** | `Admin/auth/login.php` مع `is_renewal=1` | إذا سُرقت البصمة وكلمة المرور، يمكن الدخول بدون OTP |
+| **كشف البصمة في الأخطاء** | `otp_helper.dart` | لا يوجد تدقيق كافٍ في التقاط الأخطاء |
+
+---
+
+## قائمة الإصلاحات الأمنية المُنفَّذة
+
+### 1. ✅ `Admin/auth/register.php` — استبدال ID الضعيف
+
+**قبل الإصلاح:**
+```php
+$uniqueId = rand(100000000, 999999999); // غير آمن، قد يتكرر
+```
+
+**بعد الإصلاح:**
+```php
+$uniqueId = bin2hex(random_bytes(16)); // UUID آمن (32 حرف hex عشوائي)
+```
+
+### 2. ✅ `Admin/Staff/add.php` — تفعيل التحقق من الصلاحيات
+
+**قبل الإصلاح:**
+```php
+// التحقق معلق (Commented Out)
+// $auth = JwtService::authenticate($redis);
+// if ($auth['role'] !== 'super_admin' && $auth['role'] !== 'admin') { ... }
+```
+
+**بعد الإصلاح:**
+```php
+$jwtService = new JwtService($redis);
+$auth = $jwtService->authenticate();
+$authRole = $auth->role ?? '';
+if ($authRole !== 'super_admin' && $authRole !== 'admin') {
+ jsonError("غير مصرح لك. فقط المشرفون يمكنهم إضافة موظفين.");
+ exit;
+}
+```
+
+### 3. ✅ `Admin/Staff/activate.php` — إضافة JWT Authentication
+
+**قبل الإصلاح:**
+```php
+// يجب التأكد من صلاحيات الـ Super Admin هنا
+// (عادةً يتم التحقق من التوكن أو الـ Session)
+```
+
+**بعد الإصلاح:**
+```php
+$jwtService = new JwtService($redis);
+$auth = $jwtService->authenticate();
+$authRole = $auth->role ?? '';
+if ($authRole !== 'super_admin' && $authRole !== 'admin') {
+ jsonError("غير مصرح لك. فقط المشرف العام يمكنه تفعيل الحسابات.");
+ exit;
+}
+```
+
+### 4. ✅ `Admin/auth/login.php` — إضافة Rate Limiting + رفع OTP إلى 6 أرقام
+
+```php
+// Rate Limiting قبل بدء المعالجة
+$rateLimiter = new RateLimiter($redis);
+$rateLimiter->enforce(RateLimiter::identifier(), 'login');
+
+// ... لاحقاً عند توليد OTP ...
+$otp = rand(100000, 999999); // 6 أرقام بدلاً من 5
+```
+
+### 5. ✅ `serviceapp/login.php` — إضافة Rate Limiting
+
+```php
+$rateLimiter = new RateLimiter($redis);
+$rateLimiter->enforce(RateLimiter::identifier(), 'login');
+```
+
+### 6. ✅ `Admin/auth/verify_login.php` — إضافة Rate Limiting لـ OTP
+
+```php
+$rateLimiter = new RateLimiter($redis);
+$rateLimiter->enforce(RateLimiter::identifier(), 'otp'); // 3 محاولات/5 دقائق
+```
+
+---
+
+## خلاصة
+
+```mermaid
+graph TD
+ subgraph "siro_admin (تطبيق المشرف)"
+ A[Login Page] --> B[OtpHelper.loginWithPassword]
+ B --> C{is_renewal?}
+ C -->|نعم| D[JWT مباشر]
+ C -->|لا| E[طلب OTP - 6 أرقام]
+ E --> F[Verify OTP - Rate Limited]
+ F --> G[JWT نهائي]
+ end
+
+ subgraph "Backend PHP - مؤمَّن"
+ H[Admin/auth/login.php] --> RL1[Rate Limiter ✅]
+ RL1 --> I[بحث بالبصمة/هاتف]
+ I --> J[فحص الحالة]
+ J --> K[التحقق من كلمة المرور]
+ K --> L[توليد OTP 6-digits ✅ / JWT]
+ M[verify_login.php] --> RL2[Rate Limiter (OTP) ✅]
+ RL2 --> N[التحقق من OTP]
+ N --> O[إصدار JWT]
+ P[add.php] --> AUTH1[JWT Authentication ✅]
+ Q[activate.php] --> AUTH2[JWT Authentication ✅]
+ end
+
+ subgraph "siro_service (تطبيق الخدمة)"
+ R[LoginController.login] --> S[serviceapp/login.php]
+ S --> RL3[Rate Limiter ✅]
+ RL3 --> T[JWT + HMAC]
+ T --> U[OTP للتأكيد]
+ U --> V[تسجيل دخول نهائي]
+ end
+
+ B --> H
+ F --> M
+ R --> S
+```
+
+النظام مبني على أساس أمني قوي مع تشفير متعدد الطبقات. تم إصلاح 7 ثغرات أمنية:
+
+1. **UUID آمن** بدلاً من `rand()` في تسجيل المشرفين
+2. **JWT Authentication** في add.php — يمنع أي شخص غير مصرح له من إضافة موظفين
+3. **JWT Authentication** في activate.php — يضمن أن المشرف العام فقط هو من يمكنه التفعيل
+4. **Rate Limiting** (5 محاولات/دقيقة) على login.php للمشرفين
+5. **Rate Limiting** (5 محاولات/دقيقة) على login.php لخدمة العملاء
+6. **Rate Limiting** (3 محاولات/5 دقائق) على verify_login.php (OTP)
+7. **رفع OTP إلى 6 أرقام** لزيادة صعوبة التخمين
+
+---
+
+> **تاريخ التوثيق:** 12 يونيو 2026
+> **آخر تحديث:** 12 يونيو 2026 (تم تنفيذ الإصلاحات)
+> **الإصدار:** 2.0
+> **المراجعة القادمة:** —
+> **المعنيون:** فريق التطوير — Siro
+
+
\ No newline at end of file
diff --git a/auth_flow_cleanup_report.md b/auth_flow_cleanup_report.md
new file mode 100644
index 0000000..d8f591b
--- /dev/null
+++ b/auth_flow_cleanup_report.md
@@ -0,0 +1,50 @@
+# Siro Driver App - Authentication Flow & Cleanup Report
+
+We traced the active routing flow of the driver app starting from the main entry point to document the actual user journey and identify unused legacy files.
+
+## 1. Active User Authentication Flow
+
+```mermaid
+graph TD
+ A[main.dart] --> B[SplashScreen]
+ B --> C{onboarding Done?}
+ C -- No --> D[OnBoardingPage]
+ C -- Yes --> E{Phone Registered?}
+ E -- No --> F[LoginCaptin]
+ E -- Yes --> G[Auto Login & Go to HomeCaptain]
+ F --> H{Terms & Location Perms?}
+ H -- No --> I[Show Agreement & Perm Pages]
+ H -- Yes --> J[PhoneNumberScreen in otp_page.dart]
+ J --> K[Send OTP via Nabih]
+ K --> L[OtpVerificationScreen]
+ L --> M{isRegistered?}
+ M -- No --> N[RegistrationView]
+ M -- Yes --> G
+```
+
+### Flow Breakdown
+1. **Splash & Initial Check**: Starting from `main.dart`, the app boots into `SplashScreen` and queries `SplashScreenController`.
+2. **Onboarding Check**: Checks `BoxName.onBoarding`. If not completed, redirects to `OnBoardingPage`.
+3. **Identity Check**: If `BoxName.phoneDriver` is null, routes to `LoginCaptin` inside `login_captin.dart`.
+4. **Permissions & Phone Screen**: `LoginCaptin` builds and checks `agreeTerms` and `locationPermission`. Once granted, it renders the `PhoneNumberScreen` widget directly (which is imported from `otp_page.dart`).
+5. **OTP Validation**: Once the 3-digit SMS OTP code is verified, `PhoneAuthHelper.verifyOtp` determines next steps:
+ - **New Captain**: Redirects to `RegistrationView` in `registration_view.dart` (which guides them through document capture and Gemini OCR extraction).
+ - **Existing Captain**: Authenticates using the standard controller credentials and redirects to the `HomeCaptain` map/dashboard page.
+
+---
+
+## 2. Identified Unused Legacy Files
+The following files are completely unreferenced by the driver app's active screens and routing controllers:
+
+### Unused Authentication Views
+- [register_page.dart](file:///Users/hamzaaleghwairyeen/development/App/Siro/siro_driver/lib/views/auth/register_page.dart) - Unused email/password signup layout (leftover from Rider app).
+- [verify_email_page.dart](file:///Users/hamzaaleghwairyeen/development/App/Siro/siro_driver/lib/views/auth/verify_email_page.dart) - Unused email OTP verification screen.
+- [register_captin.dart](file:///Users/hamzaaleghwairyeen/development/App/Siro/siro_driver/lib/views/auth/captin/register_captin.dart) - Unused captain email registration form.
+- [forget.dart](file:///Users/hamzaaleghwairyeen/development/App/Siro/siro_driver/lib/views/auth/captin/forget.dart) - Unused forgot password screen.
+
+### Unused Controllers
+- [login_controller.dart](file:///Users/hamzaaleghwairyeen/development/App/Siro/siro_driver/lib/controller/auth/login_controller.dart) - Unreferenced auth controller.
+- [register_controller.dart](file:///Users/hamzaaleghwairyeen/development/App/Siro/siro_driver/lib/controller/auth/register_controller.dart) - Unreferenced registration logic.
+- [verify_email_controller.dart](file:///Users/hamzaaleghwairyeen/development/App/Siro/siro_driver/lib/controller/auth/verify_email_controller.dart) - Unreferenced email OTP logic.
+- [facebook_login.dart](file:///Users/hamzaaleghwairyeen/development/App/Siro/siro_driver/lib/controller/auth/facebook_login.dart) - Unused Facebook login helper.
+- [apple_sigin.dart](file:///Users/hamzaaleghwairyeen/development/App/Siro/siro_driver/lib/controller/auth/apple_sigin.dart) - Unreferenced Apple sign-in handler (now that social login is removed from driver views).
diff --git a/backend/Admin/AdminCaptain/getDriversPhonesAndTokens.php b/backend/Admin/AdminCaptain/getDriversPhonesAndTokens.php
old mode 100755
new mode 100644
diff --git a/backend/Admin/Staff/activate.php b/backend/Admin/Staff/activate.php
new file mode 100644
index 0000000..6c264c9
--- /dev/null
+++ b/backend/Admin/Staff/activate.php
@@ -0,0 +1,54 @@
+authenticate();
+ $authRole = $auth->role ?? '';
+ if ($authRole !== 'super_admin' && $authRole !== 'admin') {
+ jsonError("غير مصرح لك. فقط المشرف العام يمكنه تفعيل الحسابات.");
+ exit;
+ }
+
+ if ($type === 'admin') {
+ $stmt = $con->prepare("UPDATE adminUser SET status = 'active' WHERE id = :id AND status = 'pending'");
+ $stmt->execute([':id' => $userId]);
+
+ if ($stmt->rowCount() > 0) {
+ printSuccess(["message" => "تم تفعيل حساب المشرف بنجاح."]);
+ } else {
+ jsonError("لم يتم العثور على حساب مشرف معلق بهذا المعرف.");
+ }
+ } elseif ($type === 'service') {
+ $stmt = $con->prepare("UPDATE users SET status = 'approved' WHERE id = :id AND status = 'pending' AND user_type = 'service'");
+ $stmt->execute([':id' => $userId]);
+
+ if ($stmt->rowCount() > 0) {
+ printSuccess(["message" => "تم تفعيل حساب موظف الخدمة بنجاح."]);
+ } else {
+ jsonError("لم يتم العثور على حساب موظف خدمة معلق بهذا المعرف.");
+ }
+ } else {
+ jsonError("نوع حساب غير صالح.");
+ }
+} catch (Exception $e) {
+ error_log("[Staff Activate Error] " . $e->getMessage());
+ jsonError("خطأ في السيرفر: " . $e->getMessage());
+}
+exit();
diff --git a/backend/Admin/Staff/add.php b/backend/Admin/Staff/add.php
index b881e4f..2e22778 100644
--- a/backend/Admin/Staff/add.php
+++ b/backend/Admin/Staff/add.php
@@ -7,15 +7,14 @@ require_once __DIR__ . '/../../core/bootstrap.php';
$con = Database::get('main');
-// التحقق من الصلاحيات: فقط المشرفين يمكنهم الإضافة
-// إذا لم يكن هناك أي مدير في النظام، نسمح// تم تعطيل التحقق للسماح بإعادة التهيئة في مرحلة التطوير
-// $count = $con->query("SELECT COUNT(*) FROM adminUser")->fetchColumn();
-// if ($count > 0) die("Access Denied: Admin already initialized.");
- // $auth = JwtService::authenticate($redis);
- // if ($auth['role'] !== 'super_admin' && $auth['role'] !== 'admin') {
- // jsonError("Unauthorized. Only Admins can add staff.");
- // exit;
- // }
+// التحقق من الصلاحيات: فقط المشرفين (super_admin أو admin) يمكنهم الإضافة
+$jwtService = new JwtService($redis);
+$auth = $jwtService->authenticate();
+$authRole = $auth->role ?? '';
+if ($authRole !== 'super_admin' && $authRole !== 'admin') {
+ jsonError("غير مصرح لك. فقط المشرفون يمكنهم إضافة موظفين.");
+ exit;
+}
$name = filterRequest("name");
$phone = filterRequest("phone");
@@ -47,14 +46,20 @@ try {
if ($role === 'admin') {
// الإضافة لجدول المديرين
- $sql = "INSERT INTO adminUser (id, fingerprint, fingerprint_hash, name, password, role, created_at)
- VALUES (:id, :fp, :fp_hash, :name, :pass, :role, NOW())";
+ // التأكد من وجود عمود phone في الجدول (كإجراء احترازي لتجنب الأخطاء إذا لم يكن موجوداً)
+ try {
+ $con->exec("ALTER TABLE adminUser ADD COLUMN phone VARCHAR(255) NULL AFTER name");
+ } catch (Exception $e) { /* العمود موجود مسبقاً */ }
+
+ $sql = "INSERT INTO adminUser (id, fingerprint, fingerprint_hash, name, phone, password, role, created_at)
+ VALUES (:id, :fp, :fp_hash, :name, :phone, :pass, :role, NOW())";
$stmt = $con->prepare($sql);
$stmt->execute([
':id' => $uniqueId,
':fp' => $encFp,
':fp_hash' => $fpHash,
':name' => $encName,
+ ':phone' => $encPhone,
':pass' => $hashedPassword,
':role' => $role
]);
diff --git a/backend/Admin/Staff/pending.php b/backend/Admin/Staff/pending.php
new file mode 100644
index 0000000..dd65168
--- /dev/null
+++ b/backend/Admin/Staff/pending.php
@@ -0,0 +1,42 @@
+query("SELECT id, name, phone, role, created_at, 'admin' as type FROM adminUser WHERE status = 'pending'");
+ $admins = $stmt1->fetchAll(PDO::FETCH_ASSOC);
+
+ // فك التشفير للأسماء والأرقام للإداريين
+ foreach ($admins as &$admin) {
+ $admin['name'] = $encryptionHelper->decryptData($admin['name']) ?: $admin['name'];
+ $admin['phone'] = $encryptionHelper->decryptData($admin['phone']) ?: $admin['phone'];
+ }
+
+ // جلب موظفي الخدمة المعلقين
+ $stmt2 = $con->query("SELECT id, first_name, last_name, phone, user_type as role, created_at, 'service' as type FROM users WHERE status = 'pending' AND user_type = 'service'");
+ $services = $stmt2->fetchAll(PDO::FETCH_ASSOC);
+
+ // فك التشفير لموظفي الخدمة
+ foreach ($services as &$service) {
+ $service['name'] = trim(($encryptionHelper->decryptData($service['first_name']) ?: $service['first_name']) . ' ' . ($encryptionHelper->decryptData($service['last_name']) ?: $service['last_name']));
+ $service['phone'] = $encryptionHelper->decryptData($service['phone']) ?: $service['phone'];
+ }
+
+ $allPending = array_merge($admins, $services);
+
+ printSuccess([
+ "data" => $allPending
+ ]);
+
+} catch (Exception $e) {
+ error_log("[Staff Pending Error] " . $e->getMessage());
+ jsonError("خطأ في السيرفر: " . $e->getMessage());
+}
+exit();
diff --git a/backend/Admin/adminUser/add_invoice.php b/backend/Admin/adminUser/add_invoice.php
old mode 100755
new mode 100644
diff --git a/backend/Admin/adminUser/invoice_total.php b/backend/Admin/adminUser/invoice_total.php
old mode 100755
new mode 100644
diff --git a/backend/Admin/auth/login.php b/backend/Admin/auth/login.php
old mode 100755
new mode 100644
index e92a7d8..779ae56
--- a/backend/Admin/auth/login.php
+++ b/backend/Admin/auth/login.php
@@ -8,6 +8,7 @@ require_once __DIR__ . '/../../functions.php';
$fingerprint = filterRequest('fingerprint');
$password = filterRequest('password');
+$phone = filterRequest('phone');
$audience = filterRequest('aud') ?? 'admin';
$isRenewal = filterRequest('is_renewal') === '1';
@@ -16,6 +17,10 @@ if (empty($fingerprint) || empty($password)) {
exit;
}
+// Rate Limiting: 5 محاولات في الدقيقة لكل IP
+$rateLimiter = new RateLimiter($redis);
+$rateLimiter->enforce(RateLimiter::identifier(), 'login');
+
try {
$con = Database::get('main');
@@ -25,6 +30,28 @@ try {
$stmt->execute([':fp' => $fpHash]);
$admin = $stmt->fetch(PDO::FETCH_ASSOC);
+ // إذا لم يتم العثور بالبصمة، وتم تمرير رقم الهاتف (تسجيل دخول لأول مرة أو جهاز جديد)
+ if (!$admin && !empty($phone)) {
+ $encPhoneInput = $encryptionHelper->encryptData($phone);
+ $stmtPhone = $con->prepare("SELECT * FROM adminUser WHERE phone = :phone LIMIT 1");
+ $stmtPhone->execute([':phone' => $encPhoneInput]);
+ $admin = $stmtPhone->fetch(PDO::FETCH_ASSOC);
+
+ // تأكيد كلمة المرور وتحديث بصمة الجهاز إذا تم إيجاد الحساب
+ if ($admin && password_verify($password, $admin['password'])) {
+ $encFpRaw = $encryptionHelper->encryptData($fingerprint);
+ $updateStmt = $con->prepare("UPDATE adminUser SET fingerprint = :fp_raw, fingerprint_hash = :fp WHERE id = :id");
+ $updateStmt->execute([
+ ':fp_raw' => $encFpRaw,
+ ':fp' => $fpHash,
+ ':id' => $admin['id']
+ ]);
+ $admin['fingerprint_hash'] = $fpHash; // Update locally
+ } else if ($admin) {
+ // Password incorrect, fail later.
+ }
+ }
+
if ($admin) {
// 1. التحقق من حالة الحساب
if ($admin['status'] === 'pending') {
@@ -69,8 +96,8 @@ try {
exit;
}
- // 3. توليد رمز تحقق OTP وإرساله عبر WhatsApp
- $otp = rand(10000, 99999);
+ // 3. توليد رمز تحقق OTP (6 أرقام) وإرساله عبر WhatsApp
+ $otp = rand(100000, 999999);
$encryptedPhone = $admin['phone'] ?? '';
if (empty($encryptedPhone)) {
@@ -112,7 +139,7 @@ try {
jsonError("كلمة المرور غير صحيحة.");
}
} else {
- jsonError("الجهاز غير مسجل كمشرف.");
+ jsonError("الحساب أو الجهاز غير مسجل. يرجى إدخال رقم هاتفك وكلمة المرور إذا كان هذا أول تسجيل دخول لك.");
}
} catch (Exception $e) {
error_log("[Admin Login Error] " . $e->getMessage());
diff --git a/backend/Admin/auth/register.php b/backend/Admin/auth/register.php
index 693d862..20faf11 100644
--- a/backend/Admin/auth/register.php
+++ b/backend/Admin/auth/register.php
@@ -1,9 +1,10 @@
prepare("SELECT id FROM adminUser WHERE phone = ? OR fingerprint_hash = ? LIMIT 1");
- $check->execute([$phone, $fpHash]);
+ // 1. التحقق من البيئة (Environment Whitelist)
+ $allowedPhonesStr = getenv('AUTHORIZED_ADMIN_PHONES');
+ if (!$allowedPhonesStr) {
+ // في حال لم يتم إعداد المتغير، نرفض الجميع للأمان
+ jsonError("غير مصرح لك بالتسجيل كمشرف (القائمة البيضاء غير معدة).");
+ exit;
+ }
- if ($check->rowCount() > 0) {
- jsonError("هذا الحساب أو الجهاز مسجل مسبقاً.");
+ $allowedPhones = array_map('trim', explode(',', $allowedPhonesStr));
+ if (!in_array($phone, $allowedPhones)) {
+ jsonError("أنت غير مصرح لك بالتسجيل كمشرف. يرجى مراجعة الإدارة.");
exit;
}
- // 2. تجهيز البيانات
- $id = bin2hex(random_bytes(16));
- $hashedPassword = password_hash($password, PASSWORD_DEFAULT);
- $encName = $encryptionHelper->encryptData($name);
- $encFp = $encryptionHelper->encryptData($fingerprint);
+ $con = Database::get('main');
- // 3. الإدخال في قاعدة البيانات (الحالة الافتراضية هي pending)
- $sql = "INSERT INTO adminUser (id, name, phone, password, fingerprint, fingerprint_hash, role, status, created_at)
- VALUES (:id, :name, :phone, :pass, :fp, :fp_hash, 'admin', 'pending', NOW())";
+ // 2. التحقق من عدم وجود الحساب مسبقاً (عن طريق الهاتف أو البصمة)
+ $fpHash = hash('sha256', $fingerprint);
+ $encPhoneInput = $encryptionHelper->encryptData($phone);
+
+ $check = $con->prepare("SELECT id FROM adminUser WHERE phone = ? OR fingerprint_hash = ? LIMIT 1");
+ $check->execute([$encPhoneInput, $fpHash]);
+
+ if ($check->rowCount() > 0) {
+ jsonError("رقم الهاتف أو الجهاز مسجل مسبقاً.");
+ exit;
+ }
+
+ // 3. تجهيز البيانات
+ $uniqueId = bin2hex(random_bytes(16)); // UUID آمن (32 حرف hex عشوائي)
+ $hashedPassword = password_hash($password, PASSWORD_DEFAULT);
+
+ $encName = $encryptionHelper->encryptData($name);
+ $encPhone = $encPhoneInput;
+ $encFp = $encryptionHelper->encryptData($fingerprint);
+
+ // التأكد من وجود عمود phone و status في الجدول
+ try {
+ $con->exec("ALTER TABLE adminUser ADD COLUMN phone VARCHAR(255) NULL AFTER name");
+ $con->exec("ALTER TABLE adminUser ADD COLUMN status VARCHAR(50) DEFAULT 'pending' AFTER role");
+ } catch (Exception $e) { /* الأعمدة موجودة مسبقاً */ }
+
+ // 4. الإدخال في قاعدة البيانات بحالة pending
+ $sql = "INSERT INTO adminUser (id, fingerprint, fingerprint_hash, name, phone, password, role, status, created_at)
+ VALUES (:id, :fp, :fp_hash, :name, :phone, :pass, 'admin', 'pending', NOW())";
$stmt = $con->prepare($sql);
$stmt->execute([
- ':id' => $id,
- ':name' => $encName,
- ':phone' => $phone,
- ':pass' => $hashedPassword,
+ ':id' => $uniqueId,
':fp' => $encFp,
- ':fp_hash' => $fpHash
+ ':fp_hash' => $fpHash,
+ ':name' => $encName,
+ ':phone' => $encPhone,
+ ':pass' => $hashedPassword
]);
printSuccess([
"status" => "pending",
- "message" => "تم تقديم طلب التسجيل بنجاح. يرجى انتظار موافقة الإدارة."
+ "message" => "تم تسجيل حسابك بنجاح وهو الآن قيد المراجعة. يرجى انتظار تفعيل المشرف العام."
]);
} catch (Exception $e) {
error_log("[Admin Register Error] " . $e->getMessage());
jsonError("خطأ في السيرفر: " . $e->getMessage());
}
+
+exit();
diff --git a/backend/Admin/auth/send_otp_admin.php b/backend/Admin/auth/send_otp_admin.php
old mode 100755
new mode 100644
diff --git a/backend/Admin/auth/verify_login.php b/backend/Admin/auth/verify_login.php
index 0a11157..4931dc8 100644
--- a/backend/Admin/auth/verify_login.php
+++ b/backend/Admin/auth/verify_login.php
@@ -15,6 +15,10 @@ if (empty($otp) || empty($fingerprint)) {
exit;
}
+// Rate Limiting: 3 محاولات OTP في 5 دقائق لكل IP
+$rateLimiter = new RateLimiter($redis);
+$rateLimiter->enforce(RateLimiter::identifier(), 'otp');
+
try {
$con = Database::get('main');
diff --git a/backend/Admin/auth/verify_otp_admin.php b/backend/Admin/auth/verify_otp_admin.php
old mode 100755
new mode 100644
diff --git a/backend/Admin/driver/deleteCaptain.php b/backend/Admin/driver/deleteCaptain.php
old mode 100755
new mode 100644
diff --git a/backend/Admin/driver/deleteRecord.php b/backend/Admin/driver/deleteRecord.php
old mode 100755
new mode 100644
diff --git a/backend/Admin/driver/find_driver_by_phone.php b/backend/Admin/driver/find_driver_by_phone.php
old mode 100755
new mode 100644
diff --git a/backend/Admin/driver/getBestDriver.php b/backend/Admin/driver/getBestDriver.php
old mode 100755
new mode 100644
diff --git a/backend/Admin/driver/getDriverGiftPayment.php b/backend/Admin/driver/getDriverGiftPayment.php
old mode 100755
new mode 100644
diff --git a/backend/Admin/driver/remove_from_blacklist.php b/backend/Admin/driver/remove_from_blacklist.php
old mode 100755
new mode 100644
diff --git a/backend/Admin/driver/updateDriverFromAdmin.php b/backend/Admin/driver/updateDriverFromAdmin.php
old mode 100755
new mode 100644
diff --git a/backend/Admin/employee/add.php b/backend/Admin/employee/add.php
old mode 100755
new mode 100644
diff --git a/backend/Admin/employee/get.php b/backend/Admin/employee/get.php
old mode 100755
new mode 100644
diff --git a/backend/Admin/error/error_list_last20.php b/backend/Admin/error/error_list_last20.php
old mode 100755
new mode 100644
diff --git a/backend/Admin/error/error_search_by_phone.php b/backend/Admin/error/error_search_by_phone.php
old mode 100755
new mode 100644
diff --git a/backend/Admin/errorApp.php b/backend/Admin/errorApp.php
old mode 100755
new mode 100644
diff --git a/backend/Admin/facebook.php b/backend/Admin/facebook.php
old mode 100755
new mode 100644
diff --git a/backend/Admin/jwtService.php b/backend/Admin/jwtService.php
old mode 100755
new mode 100644
index 16f46d9..9d203b5
--- a/backend/Admin/jwtService.php
+++ b/backend/Admin/jwtService.php
@@ -45,8 +45,8 @@ try {
$startTime = microtime(true);
- // دعم password_verify مع البقاء على التوافق مع كلمات السر القديمة (Plain Text)
- if ($user && (password_verify($password, $user['password']) || $user['password'] === $password)) {
+ // التحقق من كلمة المرور باستخدام password_hash فقط (الأمان)
+ if ($user && password_verify($password, $user['password'])) {
$limiter->reset(RateLimiter::identifier(), 'login');
diff --git a/backend/Admin/passenger/admin_delete_and_blacklist_passenger.php b/backend/Admin/passenger/admin_delete_and_blacklist_passenger.php
old mode 100755
new mode 100644
diff --git a/backend/Admin/passenger/admin_unblacklist.php b/backend/Admin/passenger/admin_unblacklist.php
old mode 100755
new mode 100644
diff --git a/backend/Admin/passenger/admin_update_passenger.php b/backend/Admin/passenger/admin_update_passenger.php
old mode 100755
new mode 100644
diff --git a/backend/Admin/rides/admin_get_rides_by_phone.php b/backend/Admin/rides/admin_get_rides_by_phone.php
old mode 100755
new mode 100644
diff --git a/backend/Admin/rides/admin_update_ride_status.php b/backend/Admin/rides/admin_update_ride_status.php
old mode 100755
new mode 100644
diff --git a/backend/Admin/rides/get_driver_live_pos.php b/backend/Admin/rides/get_driver_live_pos.php
old mode 100755
new mode 100644
diff --git a/backend/Admin/rides/get_rides_by_status.php b/backend/Admin/rides/get_rides_by_status.php
old mode 100755
new mode 100644
diff --git a/backend/Admin/rides/monitorRide.php b/backend/Admin/rides/monitorRide.php
old mode 100755
new mode 100644
diff --git a/backend/Admin/send_whatsapp_message.php b/backend/Admin/send_whatsapp_message.php
old mode 100755
new mode 100644
diff --git a/backend/Admin/view_errors.php b/backend/Admin/view_errors.php
old mode 100755
new mode 100644
diff --git a/backend/auth/captin/error_log b/backend/auth/captin/error_log
deleted file mode 100644
index 7e42402..0000000
--- a/backend/auth/captin/error_log
+++ /dev/null
@@ -1,15 +0,0 @@
-[21-May-2025 12:28:44 Europe/Berlin] PHP Fatal error: Uncaught PDOException: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'driver.education' in 'field list' in /home2/seferli1/server.sefer.live/sefer.click/sefer/auth/captin/loginFromGoogle.php:43
-Stack trace:
-#0 /home2/seferli1/server.sefer.live/sefer.click/sefer/auth/captin/loginFromGoogle.php(43): PDO->prepare('SELECT\n driv...')
-#1 {main}
- thrown in /home2/seferli1/server.sefer.live/sefer.click/sefer/auth/captin/loginFromGoogle.php on line 43
-[21-May-2025 21:09:18 Europe/Berlin] PHP Fatal error: Uncaught PDOException: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'driver.education' in 'field list' in /home2/seferli1/server.sefer.live/sefer.click/sefer/auth/captin/loginFromGoogle.php:43
-Stack trace:
-#0 /home2/seferli1/server.sefer.live/sefer.click/sefer/auth/captin/loginFromGoogle.php(43): PDO->prepare('SELECT\n driv...')
-#1 {main}
- thrown in /home2/seferli1/server.sefer.live/sefer.click/sefer/auth/captin/loginFromGoogle.php on line 43
-[22-May-2025 03:30:03 Europe/Berlin] PHP Fatal error: Uncaught PDOException: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'driver.education' in 'field list' in /home2/seferli1/server.sefer.live/sefer.click/sefer/auth/captin/loginFromGoogle.php:43
-Stack trace:
-#0 /home2/seferli1/server.sefer.live/sefer.click/sefer/auth/captin/loginFromGoogle.php(43): PDO->prepare('SELECT\n driv...')
-#1 {main}
- thrown in /home2/seferli1/server.sefer.live/sefer.click/sefer/auth/captin/loginFromGoogle.php on line 43
diff --git a/backend/auth/captin/forgetPassword.php b/backend/auth/captin/forgetPassword.php
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/auth/captin/getAllDriverSecure.php b/backend/auth/captin/getAllDriverSecure.php
deleted file mode 100644
index f566663..0000000
--- a/backend/auth/captin/getAllDriverSecure.php
+++ /dev/null
@@ -1,39 +0,0 @@
-prepare($sql);
-$stmt->execute();
-
-if ($stmt->rowCount() > 0) {
- $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
-
- // فك تشفير الحقول الحساسة
- foreach ($rows as &$row) {
- $row['phone'] = $encryptionHelper->decryptData($row['phone']);
- $row['email'] = $encryptionHelper->decryptData($row['email']);
- $row['gender'] = $encryptionHelper->decryptData($row['gender']);
- $row['birthdate'] = $encryptionHelper->decryptData($row['birthdate']);
- $row['first_name'] = $encryptionHelper->decryptData($row['first_name']);
- $row['last_name'] = $encryptionHelper->decryptData($row['last_name']);
- $row['sosPhone'] = $encryptionHelper->decryptData($row['sosPhone']);
- }
-
- jsonSuccess($rows);
-} else {
- jsonError("No wallet record found");
-}
-?>
\ No newline at end of file
diff --git a/backend/auth/captin/loginFromGoogle.php b/backend/auth/captin/loginFromGoogle.php
old mode 100755
new mode 100644
diff --git a/backend/auth/captin/loginUsingCredentialsWithoutGoogle.php b/backend/auth/captin/loginUsingCredentialsWithoutGoogle.php
old mode 100755
new mode 100644
diff --git a/backend/auth/captin/register.php b/backend/auth/captin/register.php
deleted file mode 100755
index bf67bcc..0000000
--- a/backend/auth/captin/register.php
+++ /dev/null
@@ -1,132 +0,0 @@
-encryptData($data[$f]);
- }
- }
-
- /* =========== 3) توليد driver ID (id) إذا لم يُرسَل =========== */
-
-
- /* =========== 4) هَش كلمة المرور =========== */
- $data['password_hashed'] = password_hash($data['password'], PASSWORD_DEFAULT);
-
- /* =========== 5) منع التكرار في الهاتف / الإيميل =========== */
- $dup = $con->prepare(
- "SELECT id FROM driver WHERE phone = :phone OR email = :email"
- );
- $dup->execute([
- ':phone' => $data['phone'],
- ':email' => $data['email']
- ]);
- if ($dup->rowCount() > 0) {
- jsonError("Phone or email already registered.");
- exit;
- }
-
- /* =========== 6) إدخال السجل الجديد =========== */
- $sql = "
- INSERT INTO driver (
- id, phone, email, password, gender, license_type, national_number,
- name_arabic, issue_date, expiry_date, license_categories,
- address, licenseIssueDate, status, birthdate, site,
- first_name, last_name, accountBank, bankCode,
- employmentType, maritalStatus, fullNameMaritial, expirationDate,
- created_at, updated_at
- ) VALUES (
- :id, :phone, :email, :pwd, :gender, :license_type, :national_number,
- :name_arabic, :issue_date, :expiry_date, :license_categories,
- :address, :licenseIssueDate, :status, :birthdate, :site,
- :first_name, :last_name, :accountBank, :bankCode,
- :employmentType, :maritalStatus, :fullNameMaritial, :expirationDate,
- NOW(), NOW()
- )
- ";
-
- $ins = $con->prepare($sql);
-
- // خريطة الربط (تطابق تمامًا أسماء الـ placeholders في الـ SQL أعلاه)
- $bind = [
- 'id' => $data['id'],
- 'phone' => $data['phone'],
- 'email' => $data['email'],
- 'pwd' => $data['password_hashed'],
- 'gender' => $data['gender'],
- 'license_type' => $data['license_type'],
- 'national_number' => $data['national_number'],
- 'name_arabic' => $data['name_arabic'],
- 'issue_date' => $data['issue_date'],
- 'expiry_date' => $data['expiry_date'],
- 'license_categories'=> $data['license_categories']?? 'B',
- 'address' => $data['address'],
- 'licenseIssueDate' => $data['licenseIssueDate'],
- 'status' => $data['status'] ?? 'yet',
- 'birthdate' => $data['birthdate'],
- 'site' => $data['site'],
- 'first_name' => $data['first_name'],
- 'last_name' => $data['last_name'],
- 'accountBank' => 'yet',
- 'bankCode' => 'yet',
- 'employmentType' => $data['employmentType']?? 'yet',
- 'maritalStatus' => $data['maritalStatus']?? 'yet',
- 'fullNameMaritial' => $data['fullNameMaritial']?? 'yet',
- 'expirationDate' => $data['expirationDate']?? 'yet',
- ];
-
- foreach ($bind as $key => $value) {
- $ins->bindValue(":$key", $value);
- }
-
- if ($ins->execute()) {
- jsonSuccess($data['id']); // ترجع driver ID
- } else {
- jsonError("Failed to insert driver record.");
- }
-
-} catch (PDOException $e) {
- error_log("DriverInsert PDO: " . $e->getMessage());
- jsonError("Database error.");
-}
-?>
\ No newline at end of file
diff --git a/backend/auth/captin/sendOtpMessageDriver.php b/backend/auth/captin/sendOtpMessageDriver.php
deleted file mode 100755
index 69c51b3..0000000
--- a/backend/auth/captin/sendOtpMessageDriver.php
+++ /dev/null
@@ -1,140 +0,0 @@
-exists($redisKey)) {
- jsonError("Please wait before requesting a new OTP.");
- exit;
- }
- $redis->setex($redisKey, 60, "1"); // حظر لمدة 60 ثانية
-}
-
-// توليد رمز تحقق مكوّن من 5 أرقام
-$token_code = str_pad(random_int(0, 99999), 5, '0', STR_PAD_LEFT);
-
-// تشفير البيانات الحساسة
-$encryptedPhone = $encryptionHelper->encryptData($phone_number);
-$encryptedToken = $encryptionHelper->encryptData($token_code);
-$encryptedEmail = $encryptionHelper->encryptData($email); // اختياري إذا بتحب تشفيره
-
-// التحقق من وجود الرقم مسبقاً في قاعدة البيانات
-$sqlCheck = "SELECT * FROM `phone_verification` WHERE `phone_number` = :phone";
-$stmtCheck = $con->prepare($sqlCheck);
-$stmtCheck->bindParam(":phone", $encryptedPhone);
-$stmtCheck->execute();
-
-$success = false;
-
-// إذا كان الرقم موجود → تحديث
-if ($stmtCheck->rowCount() > 0) {
- $sqlUpdate = "UPDATE `phone_verification`
- SET `token_code` = :token,
- `expiration_time` = DATE_ADD(NOW(), INTERVAL 5 MINUTE)
- WHERE `phone_number` = :phone";
- $stmt = $con->prepare($sqlUpdate);
- $stmt->bindParam(":token", $encryptedToken);
- $stmt->bindParam(":phone", $encryptedPhone);
- $stmt->execute();
- $success = $stmt->rowCount() > 0;
-} else {
- // إذا الرقم غير موجود → إدخال جديد
- $sqlInsert = "INSERT INTO `phone_verification`
- (`phone_number`, `driverId`, `email`, `token_code`, `expiration_time`, `is_verified`, `created_at`)
- VALUES
- (:phone, :driverId, :email, :token, DATE_ADD(NOW(), INTERVAL 5 MINUTE), 0, NOW())";
- $stmt = $con->prepare($sqlInsert);
- $stmt->bindParam(":phone", $encryptedPhone);
- $stmt->bindParam(":driverId", $driverId);
- $stmt->bindParam(":email", $encryptedEmail);
- $stmt->bindParam(":token", $encryptedToken);
- $stmt->execute();
- $success = $stmt->rowCount() > 0;
-}
-
-// إذا تم الحفظ بنجاح → أرسل الرمز عبر SMS
-if ($success) {
- // تحميل بيانات الاتصال بالـ SMS API من المتغيرات البيئية
- $username = getenv('SMS_USERNAME');
- $password = getenv('SMS_PASSWORD_EGYPT');
- $sender = getenv('SMS_SENDER');
-
- if (!$username || !$password || !$sender) {
- jsonError("SMS credentials are missing");
- exit;
- }
-
- $message = "Tripz app code is " . $token_code;
- $receiver = $phone_number;
-
- $apiUrl = 'https://sms.kazumi.me/api/sms/send-sms';
- $payload = [
- 'username' => $username,
- 'password' => $password,
- 'language' => 'e',
- 'sender' => $sender,
- 'receiver' => $receiver,
- 'message' => $message
- ];
-
- $jsonPayload = json_encode($payload);
- $smsResponse = callAPI("POST", $apiUrl, $jsonPayload);
-
- if ($smsResponse) {
- jsonSuccess(null, "Verification code sent and saved successfully");
- } else {
- jsonError("Code saved, but SMS sending failed");
- }
-} else {
- jsonError("Failed to save verification data");
-}
-
-// دالة الاتصال بالـ API
-function callAPI($method, $url, $data) {
- $curl = curl_init();
- curl_setopt_array($curl, [
- CURLOPT_URL => $url,
- CURLOPT_RETURNTRANSFER => true,
- CURLOPT_CUSTOMREQUEST => $method,
- CURLOPT_POSTFIELDS => $data,
- CURLOPT_HTTPHEADER => [
- "Content-Type: application/json",
- "Accept: application/json"
- ],
- CURLOPT_TIMEOUT => 30,
- CURLOPT_CONNECTTIMEOUT => 10
- ]);
-
- $api_raw_response = curl_exec($curl);
-
- if (curl_errno($curl)) {
- error_log("cURL Error [".curl_errno($curl)."]: " . curl_error($curl));
- curl_close($curl);
- return false;
- }
-
- curl_close($curl);
- $decoded_response = json_decode($api_raw_response, true);
-
- if (json_last_error() !== JSON_ERROR_NONE) {
- error_log("Invalid JSON response from SMS API.");
- return false;
- }
-
- error_log("SMS API response: " . print_r($decoded_response, true));
- return $decoded_response;
-}
-?>
\ No newline at end of file
diff --git a/backend/auth/captin/updateDriverClaim.php b/backend/auth/captin/updateDriverClaim.php
old mode 100755
new mode 100644
diff --git a/backend/auth/captin/updateDriverSecure.php b/backend/auth/captin/updateDriverSecure.php
deleted file mode 100644
index 0597d86..0000000
--- a/backend/auth/captin/updateDriverSecure.php
+++ /dev/null
@@ -1,56 +0,0 @@
-encryptData($value);
- $columnValues[] = "`$field` = ?";
- $params[] = $encryptedValue;
- }
-}
-
-// تحقق من أن هناك حقول للتحديث
-if (empty($columnValues)) {
- jsonError("No valid encrypted passenger data provided for update.");
- exit;
-}
-
-// تركيب جملة SQL
-$setClause = implode(", ", $columnValues);
-$params[] = $id;
-
-$sql = "UPDATE `passengers` SET $setClause WHERE `id` = ?";
-
-try {
- $stmt = $con->prepare($sql);
-
- foreach ($params as $index => $value) {
- $stmt->bindValue($index + 1, $value);
- }
-
- if ($stmt->execute()) {
- jsonSuccess(null, "Passenger data updated successfully with encryption");
- } else {
- jsonError("Failed to update passenger data");
- }
-
-} catch (PDOException $e) {
- jsonError("Database error: " . $e->getMessage());
-}
-?>
\ No newline at end of file
diff --git a/backend/auth/captin/updateShamCashDriver.php b/backend/auth/captin/updateShamCashDriver.php
old mode 100755
new mode 100644
diff --git a/backend/auth/captin/verifyEmail.php b/backend/auth/captin/verifyEmail.php
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/auth/captin/verifyOtpDriver.php b/backend/auth/captin/verifyOtpDriver.php
deleted file mode 100755
index 0d6ba7b..0000000
--- a/backend/auth/captin/verifyOtpDriver.php
+++ /dev/null
@@ -1,39 +0,0 @@
-encryptData($phone_number);
-$encryptedToken = $encryptionHelper->encryptData($token_code);
-
-// Check if the phone number and token code match
-$sql = "SELECT
- `id`,
- `phone_number`,
- `token_code`,
- `expiration_time`,
- `is_verified`,
- `created_at`
-FROM
- `phone_verification`
-WHERE
- `phone_number` = :phone_number AND `token_code` = :token_code -- AND `expiration_time` > NOW()";
-$stmt = $con->prepare($sql);
-$stmt->bindParam(':phone_number', $encryptedPhone, PDO::PARAM_STR);
-$stmt->bindParam(':token_code', $encryptedToken, PDO::PARAM_STR);
-$stmt->execute();
-$result = $stmt->fetch();
-
-if ($result) {
- // $id = $result["id"];
- $sql = "UPDATE `phone_verification` SET `is_verified` = 1 WHERE `phone_number` = :phone_number";
- $stmt = $con->prepare($sql);
- $stmt->bindParam(':phone_number', $phone_number, PDO::PARAM_STR);
- $stmt->execute();
-
- jsonSuccess($message = "Your phone number has been verified.");
-} else {
- jsonError($message = "Your phone number could not be verified. Please try again.");
-}
-?>
\ No newline at end of file
diff --git a/backend/auth/checkPhoneNumberISVerfiedPassenger.php b/backend/auth/checkPhoneNumberISVerfiedPassenger.php
old mode 100755
new mode 100644
diff --git a/backend/auth/cnMap.php b/backend/auth/cnMap.php
deleted file mode 100644
index 68f4df5..0000000
--- a/backend/auth/cnMap.php
+++ /dev/null
@@ -1,23 +0,0 @@
- "3",
- "1" => "7",
- "2" => "1",
- "3" => "9",
- "4" => "0",
- "5" => "5",
- "6" => "2",
- "7" => "6",
- "8" => "4",
- "9" => "8"
-);
-
-// Convert the map to a JSON string with JSON_FORCE_OBJECT option
-$jsonString = json_encode($cn, JSON_FORCE_OBJECT);
-
-// Send the JSON string to the Flutter app
-echo $jsonString;
-?>
diff --git a/backend/auth/cn_map.json b/backend/auth/cn_map.json
deleted file mode 100644
index c398c06..0000000
--- a/backend/auth/cn_map.json
+++ /dev/null
@@ -1 +0,0 @@
-["3","7","1","9","0","5","2","6","4","8"]
\ No newline at end of file
diff --git a/backend/auth/document_syria/ai_document.php b/backend/auth/document_syria/ai_document.php
old mode 100755
new mode 100644
diff --git a/backend/auth/document_syria/uploadDocSyria.php b/backend/auth/document_syria/uploadDocSyria.php
deleted file mode 100755
index d5e26e9..0000000
--- a/backend/auth/document_syria/uploadDocSyria.php
+++ /dev/null
@@ -1,115 +0,0 @@
- $_FILES['image']['name'] ?? 'unknown',
- 'type' => $_FILES['image']['type'] ?? 'unknown',
- 'size' => $_FILES['image']['size'] ?? 0,
- 'upload_error_code' => $_FILES['image']['error'] ?? UPLOAD_ERR_OK
- ]);
-} else {
- uploadLog("No 'image' file was sent in the request.", 'WARNING');
-}
-
-if (!isset($_FILES['image']) || $_FILES['image']['error'] !== UPLOAD_ERR_OK) {
- $err = $_FILES['image']['error'] ?? 'missing_file';
- uploadLog("❌ File upload validation failed. Code: $err", 'ERROR');
- error_log("Upload error: Image not provided or upload failed.");
- jsonError("Image upload failed");
- exit;
-}
-
-$file = $_FILES['image'];
-
-// ✅ السماح بالامتدادات الشائعة + فحص MIME الحقيقي
-$allowedExt = ['jpg', 'jpeg', 'png'];
-$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
-if (!in_array($extension, $allowedExt, true)) {
- uploadLog("❌ Unsupported file extension: $extension", 'ERROR');
- error_log("Unsupported file extension: $extension");
- jsonError("Unsupported file type");
- exit;
-}
-
-// فحص نوع المحتوى الفعلي (أكثر أماناً)
-$finfo = new finfo(FILEINFO_MIME_TYPE);
-$mime = $finfo->file($file['tmp_name']) ?: 'application/octet-stream';
-$allowedMime = ['image/jpeg', 'image/png'];
-if (!in_array($mime, $allowedMime, true)) {
- error_log("Unsupported MIME type: $mime");
- jsonError("Unsupported image MIME type");
- exit;
-}
-
-// (اختياري) حد أقصى للحجم 10MB
-$maxBytes = 10 * 1024 * 1024;
-if ($file['size'] > $maxBytes) {
- error_log("Image too large: {$file['size']} bytes");
- jsonError("Image too large (max 10MB)");
- exit;
-}
-
-// 📁 مسارات الحفظ
-$uploadDir = "../uploads/documents/";
-if (!is_dir($uploadDir)) {
- if (!mkdir($uploadDir, 0755, true) && !is_dir($uploadDir)) {
- error_log("Failed to create upload directory: $uploadDir");
- jsonError("Server error: cannot create upload directory");
- exit;
- }
-}
-
-
-$baseName = "driver_{$type}_{$driverId}";
-$uniqueName = $baseName . "." . $extension;
-$uploadPath = $uploadDir . $uniqueName;
-
-// ⬆️ نقل الملف
-if (!move_uploaded_file($file['tmp_name'], $uploadPath)) {
- error_log("Failed to move uploaded file to $uploadPath");
- jsonError("Failed to move uploaded image");
- exit;
-}
-
-// 🔒 منع التنفيذ لو رُفع PHP بالخطأ
-@chmod($uploadPath, 0644);
-
-// 🌐 توليد BASE_URL آمن (يدعم ENV أو يعتمد على المضيف الحالي)
-if (!defined('BASE_URL')) {
- $APP_BASE_URL = rtrim(getenv('APP_BASE_URL') ?: '', '/');
- if ($APP_BASE_URL === '') {
- $scheme = isset($_SERVER['REQUEST_SCHEME']) ? $_SERVER['REQUEST_SCHEME'] : ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http');
- $host = $_SERVER['HTTP_HOST'] ?? 'localhost';
- define('BASE_URL', $scheme . '://' . $host);
- } else {
- define('BASE_URL', $APP_BASE_URL);
- }
-}
-
-// ⚙️ مسار الرابط العام (عدّل المسار حسب نشر مشروعك)
-$publicPath = "/siro/auth/uploads/documents/" . $uniqueName;
-$imageUrl = rtrim(BASE_URL, '/') . $publicPath;
-
-// ✅ نتيجة نهائية: فقط رابط الصورة وبعض البيانات المفيدة
-uploadLog("✅ Document upload succeeded. URL: $imageUrl");
-printSuccess([
- $imageUrl,
-]);
\ No newline at end of file
diff --git a/backend/auth/error_log b/backend/auth/error_log
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/auth/google_auth/callback.php b/backend/auth/google_auth/callback.php
deleted file mode 100755
index 70e7e53..0000000
--- a/backend/auth/google_auth/callback.php
+++ /dev/null
@@ -1,64 +0,0 @@
- $authCode,
- 'client_id' => $clientID,
- 'client_secret' => $clientSecret,
- 'redirect_uri' => $redirectUri,
- 'grant_type' => 'authorization_code'
-];
-
-$ch = curl_init();
-curl_setopt($ch, CURLOPT_URL, $tokenUrl);
-curl_setopt($ch, CURLOPT_POST, 1);
-curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
-curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
-$response = curl_exec($ch);
-curl_close($ch);
-$tokenData = json_decode($response, true);
-
-if (isset($tokenData['access_token'])) {
- // 4. جلب بيانات المستخدم
- $userInfoUrl = 'https://www.googleapis.com/oauth2/v2/userinfo?access_token=' . $tokenData['access_token'];
- $userInfoResponse = file_get_contents($userInfoUrl);
- $userData = json_decode($userInfoResponse, true);
-
- if (isset($userData['id'])) {
- // 5. تحديث ملف الجلسة بالبيانات الجديدة
- $finalData = [
- 'status' => 'success',
- 'userData' => $userData
- ];
- file_put_contents($sessionFile, json_encode($finalData));
- }
-}
-
-// 6. عرض صفحة نجاح للمستخدم في المتصفح
-echo 'SuccessAuthentication Successful
You can now return to the Tripz app.
';
-exit();
-?>
\ No newline at end of file
diff --git a/backend/auth/google_auth/check_status.php b/backend/auth/google_auth/check_status.php
deleted file mode 100755
index 26d2649..0000000
--- a/backend/auth/google_auth/check_status.php
+++ /dev/null
@@ -1,38 +0,0 @@
- 'error', 'message' => 'Login token is missing.']);
- exit();
-}
-$loginToken = basename($input['loginToken']); // حماية
-
-// 2. التحقق من ملف الجلسة
-$pollDir = __DIR__ . '/polls';
-$sessionFile = $pollDir . '/' . $loginToken . '.json';
-
-if (file_exists($sessionFile)) {
- $sessionData = json_decode(file_get_contents($sessionFile), true);
-
- // إذا نجحت العملية، أرجع البيانات واحذف الملف
- if ($sessionData['status'] === 'success') {
- echo json_encode($sessionData);
- unlink($sessionFile); // حذف الملف بعد النجاح
- } else {
- // إذا كانت لا تزال معلقة
- echo json_encode(['status' => 'pending']);
- }
-} else {
- // إذا انتهت المهلة أو حدث خطأ
- http_response_code(404);
- echo json_encode(['status' => 'expired', 'message' => 'Session not found or expired.']);
-}
-exit();
-?>
diff --git a/backend/auth/google_auth/google_auth.php b/backend/auth/google_auth/google_auth.php
deleted file mode 100755
index 4e9362b..0000000
--- a/backend/auth/google_auth/google_auth.php
+++ /dev/null
@@ -1,55 +0,0 @@
- false,
- 'error' => null,
- 'data' => null,
-];
-
-try {
- if (!isset($_POST['code'])) {
- throw new Exception("Missing authorization code.");
- }
-
- $code = $_POST['code'];
-
- $client = new Client();
- $client->setClientId('1086900987150-j8brn0i5s97315kh1ej9jr72grkfqgh5.apps.googleusercontent.com');
- $client->setClientSecret('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');
- $client->setRedirectUri('postmessage');
- $client->addScope('email');
- $client->addScope('profile');
-
- $token = $client->fetchAccessTokenWithAuthCode($code);
-
- if (isset($token['error'])) {
- throw new Exception("Access token error: " . $token['error']);
- }
-
- $client->setAccessToken($token['access_token']);
-
- $oauth2 = new Google_Service_Oauth2($client);
- $userinfo = $oauth2->userinfo->get();
-
- $response['success'] = true;
- $response['data'] = [
- 'id' => $userinfo->id,
- 'email' => $userinfo->email,
- 'name' => $userinfo->name,
- 'picture' => $userinfo->picture,
- ];
-
-} catch (Exception $e) {
- $response['error'] = $e->getMessage();
-}
-
-echo json_encode($response);
\ No newline at end of file
diff --git a/backend/auth/google_auth/login.php b/backend/auth/google_auth/login.php
deleted file mode 100755
index 4842a1f..0000000
--- a/backend/auth/google_auth/login.php
+++ /dev/null
@@ -1,41 +0,0 @@
- 'pending']));
-
-// 5. بناء رابط جوجل مع تمرير المعرف الفريد في متغير 'state'
-$authUrl = 'https://accounts.google.com/o/oauth2/v2/auth?' . http_build_query([
- 'client_id' => $clientID,
- 'redirect_uri' => $redirectUri,
- 'response_type' => 'code',
- 'scope' => $scopes,
- 'access_type' => 'offline',
- 'state' => $loginToken // مهم جداً
-]);
-
-// 6. إرجاع الرابط والمعرف للتطبيق
-header('Content-Type: application/json');
-echo json_encode([
- 'authUrl' => $authUrl,
- 'loginToken' => $loginToken
-]);
-exit();
-?>
\ No newline at end of file
diff --git a/backend/auth/loginFromGooglePassenger.php b/backend/auth/loginFromGooglePassenger.php
old mode 100755
new mode 100644
diff --git a/backend/auth/otp/providers.php b/backend/auth/otp/providers.php
new file mode 100644
index 0000000..3ede793
--- /dev/null
+++ b/backend/auth/otp/providers.php
@@ -0,0 +1,229 @@
+ $username,
+ 'password' => $password,
+ 'language' => 'e',
+ 'sender' => $sender,
+ 'receiver' => $receiver,
+ 'message' => $message
+ ];
+
+ $response = curlCall("POST", $apiUrl, json_encode($payload), [
+ "Content-Type: application/json"
+ ]);
+
+ if ($response) {
+ $decoded = json_decode($response, true);
+ if (isset($decoded['message']) && $decoded['message'] === 'Success') {
+ return true;
+ }
+ error_log("❌ [Kazumi OTP] API returned failure response: " . $response);
+ }
+ return false;
+}
+
+/**
+ * Retrieve Nabeh JWT Bearer Token, caching it in Redis for 24 hours.
+ *
+ * @return string|null The Bearer token, or null on failure.
+ */
+function getNabehBearerToken(): ?string {
+ global $redis;
+
+ // 1. Try to read cached token from Redis (TTL 24 hours)
+ if ($redis) {
+ try {
+ $cachedToken = $redis->get('nabeh_bearer_token');
+ if ($cachedToken) {
+ return $cachedToken;
+ }
+ } catch (Exception $e) {
+ error_log("⚠️ [Nabeh Auth Redis] Error reading token: " . $e->getMessage());
+ }
+ }
+
+ // 2. Token not cached, authenticate via Nabeh Login API
+ $email = getenv('NABEH_EMAIL');
+ $password = getenv('NABEH_PASSWORD');
+
+ if (!$email || !$password) {
+ error_log("⚠️ [Nabeh Auth] Missing NABEH_EMAIL or NABEH_PASSWORD environment variables.");
+ return null;
+ }
+
+ $apiUrl = 'https://nabeh.intaleqapp.com/api/auth/login';
+ $payload = [
+ 'email' => $email,
+ 'password' => $password
+ ];
+
+ $response = curlCall("POST", $apiUrl, json_encode($payload), [
+ 'Content-Type: application/json'
+ ]);
+
+ if ($response) {
+ $decoded = json_decode($response, true);
+ $token = $decoded['token'] ?? $decoded['message']['token'] ?? $decoded['jwt'] ?? $decoded['access_token'] ?? null;
+
+ if ($token) {
+ // Cache token in Redis for 24 hours (86400 seconds)
+ if ($redis) {
+ try {
+ $redis->setex('nabeh_bearer_token', 86400, $token);
+ } catch (Exception $e) {
+ error_log("⚠️ [Nabeh Auth Redis Cache Save] Error saving token: " . $e->getMessage());
+ }
+ }
+ return $token;
+ }
+ error_log("❌ [Nabeh Auth] Failed to extract token from login response: " . $response);
+ }
+ return null;
+}
+
+/**
+ * Send OTP via Nabeh JWT Auth Gateway (WhatsApp, Voice, etc.)
+ *
+ * @param string $receiver Recipient phone number
+ * @param string $otp 3-digit verification code
+ * @param string $method text | voice | image | whatsapp
+ * @return bool True if OTP was sent successfully
+ */
+function sendNabehOtp(string $receiver, string $otp, string $method = 'text'): bool {
+ $bearerToken = getNabehBearerToken();
+ if (!$bearerToken) {
+ error_log("⚠️ [Nabeh OTP] Failed to obtain dynamic JWT Bearer token.");
+ return false;
+ }
+
+ // Strip symbols for Nabeh endpoint
+ $phoneRaw = preg_replace('/\D+/', '', $receiver);
+
+ // Map method/type
+ $type = 'text';
+ if ($method === 'voice') {
+ $type = 'voice';
+ } elseif ($method === 'image') {
+ $type = 'image';
+ }
+ // elseif ($method === 'flash_call') {
+ // $type = 'flash_call';
+ // }
+
+ $apiUrl = 'https://nabeh.intaleqapp.com/api/otp/send';
+ $payload = [
+ 'phone' => $phoneRaw,
+ 'type' => $type,
+ 'code' => $otp
+ ];
+
+ $response = curlCall("POST", $apiUrl, json_encode($payload), [
+ 'Content-Type: application/json',
+ "Authorization: Bearer $bearerToken"
+ ]);
+
+ if ($response) {
+ $decoded = json_decode($response, true);
+ if ($decoded && ($decoded['success'] ?? false)) {
+ return true;
+ }
+ error_log("❌ [Nabeh OTP] API returned failure response: " . $response);
+ }
+ return false;
+}
+
+/**
+ * Send OTP via Intaleq Static OTP Gateway (using body app_key parameter)
+ *
+ * @param string $receiver Recipient phone number
+ * @param string $otp 3-digit verification code
+ * @param string $method whatsapp | sms | voice | flash_call
+ * @return bool True if OTP was sent successfully
+ */
+function sendIntaleqOtp(string $receiver, string $otp, string $method = 'whatsapp'): bool {
+ $appKey = getenv('NABEH_OTP_APP_KEY');
+
+ if (!$appKey) {
+ error_log("⚠️ [Intaleq OTP] Missing NABEH_OTP_APP_KEY in environment.");
+ return false;
+ }
+
+ // Normalize receiver to start with +
+ $phoneWithPlus = (strpos($receiver, '+') === 0) ? $receiver : '+' . $receiver;
+
+ $apiUrl = 'https://otp.intaleqapp.com/api/request-otp.php';
+ $payload = [
+ 'phone' => $phoneWithPlus,
+ 'app_key' => $appKey,
+ 'device_type' => 'android',
+ 'method' => $method,
+ 'code' => $otp
+ ];
+
+ $response = curlCall("POST", $apiUrl, json_encode($payload), [
+ 'Content-Type: application/json'
+ ]);
+
+ if ($response) {
+ $decoded = json_decode($response, true);
+ if ($decoded && ($decoded['success'] ?? false)) {
+ return true;
+ }
+ error_log("❌ [Intaleq OTP] API returned failure response: " . $response);
+ }
+ return false;
+}
+
+/**
+ * Generic cURL execution helper
+ */
+function curlCall(string $method, string $url, string $data, array $headers): ?string {
+ $ch = curl_init($url);
+ curl_setopt_array($ch, [
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_CUSTOMREQUEST => $method,
+ CURLOPT_POSTFIELDS => $data,
+ CURLOPT_HTTPHEADER => $headers,
+ CURLOPT_TIMEOUT => 15,
+ CURLOPT_CONNECTTIMEOUT => 5
+ ]);
+
+ $response = curl_exec($ch);
+ $error = curl_error($ch);
+ $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ curl_close($ch);
+
+ if ($error) {
+ error_log("⚠️ [OTP cURL] Error calling $url: $error");
+ return null;
+ }
+
+ if ($httpCode !== 200) {
+ error_log("⚠️ [OTP cURL] Non-200 HTTP code $httpCode from $url. Response: $response");
+ }
+
+ return $response;
+}
diff --git a/backend/auth/otp/request.php b/backend/auth/otp/request.php
new file mode 100644
index 0000000..57b9d3e
--- /dev/null
+++ b/backend/auth/otp/request.php
@@ -0,0 +1,260 @@
+enforce(RateLimiter::identifier(), 'otp');
+
+// 2. Fetch input parameters
+$receiver = filterRequest("receiver");
+if (empty($receiver)) {
+ $receiver = filterRequest("phone_number");
+}
+
+$user_type = filterRequest("user_type");
+
+$authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? (function_exists('apache_request_headers') ? (apache_request_headers()['Authorization'] ?? null) : null);
+if (!empty($authHeader) && preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) {
+ $jwtToken = $matches[1];
+ $tokenParts = explode('.', $jwtToken);
+ if (count($tokenParts) === 3) {
+ $payload = json_decode(base64_decode($tokenParts[1]), true);
+ if (isset($payload['role'])) {
+ $user_type = $payload['role'];
+ }
+ }
+}
+
+$country = filterRequest("country"); // Egypt | Syria | Jordan
+$method = filterRequest("method"); // whatsapp | sms | voice | flash_call | bearer_send
+$context = filterRequest("context"); // token_change | login (default)
+
+// For driver registration context
+$driverId = filterRequest("driverId");
+$email = filterRequest("email");
+
+if (empty($receiver)) {
+ jsonError("Phone number (receiver) is required.");
+ exit;
+}
+
+// Auto-detect country if empty
+if (empty($country)) {
+ $cleanReceiver = preg_replace('/\D+/', '', $receiver);
+ if (strpos($cleanReceiver, '20') === 0 || (strlen($cleanReceiver) === 11 && strpos($cleanReceiver, '01') === 0)) {
+ $country = 'Egypt';
+ } elseif (strpos($cleanReceiver, '962') === 0 || (strlen($cleanReceiver) === 9 && strpos($cleanReceiver, '7') === 0)) {
+ $country = 'Jordan';
+ } elseif (strpos($cleanReceiver, '963') === 0 || (strlen($cleanReceiver) === 9 && strpos($cleanReceiver, '9') === 0)) {
+ $country = 'Syria';
+ } else {
+ $country = 'Jordan'; // Default fallback
+ }
+}
+
+// Auto-detect user_type if empty
+if (empty($user_type)) {
+ if (!empty($driverId) || strpos($_SERVER['REQUEST_URI'], 'driver') !== false) {
+ $user_type = 'driver';
+ } else {
+ $user_type = 'passenger';
+ }
+}
+if (empty($user_type) || !in_array($user_type, ['passenger', 'driver', 'admin', 'service'])) {
+ jsonError("User type must be 'passenger', 'driver', 'admin', or 'service'.");
+ exit;
+}
+
+if ($user_type === 'admin') {
+ $allowedPhones = explode(',', getenv('ADMIN_PHONE_NUMBERS'));
+ if (!in_array($receiver, $allowedPhones)) {
+ error_log("⚠️ [Admin OTP] Unauthorized phone number attempted: $receiver");
+ jsonError("رقم الهاتف غير مصرح له.");
+ exit;
+ }
+}
+
+// 3. Establish DB Connection
+try {
+ $con = Database::get('main');
+} catch (Exception $e) {
+ http_response_code(500);
+ exit(json_encode(['error' => 'Database connection failed']));
+}
+
+// 4. Generate 3-digit OTP code
+$otp = str_pad((string)random_int(0, 999), 3, '0', STR_PAD_LEFT);
+
+// 5. Geographical Routing & Dispatch
+$sentSuccessfully = false;
+
+switch (strtolower($country)) {
+ case 'egypt':
+ $sentSuccessfully = sendKazumiSms($receiver, $otp);
+ if (!$sentSuccessfully) {
+ error_log("⚠️ [Egypt OTP Failover 1] Kazumi SMS failed. Falling back to Intaleq OTP WhatsApp.");
+ $sentSuccessfully = sendIntaleqOtp($receiver, $otp, 'whatsapp');
+ if (!$sentSuccessfully) {
+ error_log("⚠️ [Egypt OTP Failover 2] Intaleq OTP WhatsApp failed. Falling back to Nabeh JWT OTP text.");
+ $sentSuccessfully = sendNabehOtp($receiver, $otp, 'text');
+ }
+ }
+ break;
+
+ case 'syria':
+ // Syria uses dynamic Nabeh JWT for voice/image, static Intaleq app_key for whatsapp/sms
+ if ($method === 'bearer_send' || $method === 'voice' || $method === 'image') {
+ $sentSuccessfully = sendNabehOtp($receiver, $otp, $method);
+ if (!$sentSuccessfully) {
+ error_log("⚠️ [Syria OTP Failover] Nabeh JWT method failed. Falling back to Intaleq OTP WhatsApp.");
+ $sentSuccessfully = sendIntaleqOtp($receiver, $otp, 'whatsapp');
+ }
+ } else {
+ $sentSuccessfully = sendIntaleqOtp($receiver, $otp, $method ?: 'whatsapp');
+ if (!$sentSuccessfully) {
+ error_log("⚠️ [Syria OTP Failover] Intaleq OTP WhatsApp failed. Falling back to Nabeh JWT OTP text.");
+ $sentSuccessfully = sendNabehOtp($receiver, $otp, 'text');
+ }
+ }
+ break;
+
+ case 'jordan':
+ // Jordan uses dynamic Nabeh JWT for voice/image, static Intaleq app_key for sms/whatsapp
+ if ($method === 'bearer_send' || $method === 'voice' || $method === 'image') {
+ $sentSuccessfully = sendNabehOtp($receiver, $otp, $method);
+ if (!$sentSuccessfully) {
+ error_log("⚠️ [Jordan OTP Failover] Nabeh JWT method failed. Falling back to Intaleq OTP SMS.");
+ $sentSuccessfully = sendIntaleqOtp($receiver, $otp, 'sms');
+ }
+ } else {
+ $sentSuccessfully = sendIntaleqOtp($receiver, $otp, $method ?: 'sms');
+ if (!$sentSuccessfully) {
+ error_log("⚠️ [Jordan OTP Failover] Intaleq OTP SMS failed. Falling back to Nabeh JWT OTP text.");
+ $sentSuccessfully = sendNabehOtp($receiver, $otp, 'text');
+ }
+ }
+ break;
+
+ default:
+ // Default fallback to Kazumi SMS
+ $sentSuccessfully = sendKazumiSms($receiver, $otp);
+ if (!$sentSuccessfully) {
+ error_log("⚠️ [Default OTP Failover] Kazumi SMS failed. Falling back to Intaleq OTP WhatsApp.");
+ $sentSuccessfully = sendIntaleqOtp($receiver, $otp, 'whatsapp');
+ }
+ break;
+}
+
+// 6. DB Storage on Success
+if ($sentSuccessfully) {
+ $encryptedPhone = $encryptionHelper->encryptData($receiver);
+ $encryptedOtp = $encryptionHelper->encryptData($otp);
+ $encryptedEmail = !empty($email) ? $encryptionHelper->encryptData($email) : '';
+
+ $expirationTime = date('Y-m-d H:i:s', strtotime('+5 minutes'));
+
+ try {
+ if ($user_type === 'admin') {
+ $stmt = $con->prepare("INSERT INTO token_verification_admin (phone_number, token, expiration_time)
+ VALUES (?, ?, DATE_ADD(NOW(), INTERVAL 5 MINUTE))
+ ON DUPLICATE KEY UPDATE token = VALUES(token), expiration_time = VALUES(expiration_time)");
+ $stmt->execute([$encryptedPhone, $encryptedOtp]);
+ } elseif ($user_type === 'service') {
+ $stmtDel = $con->prepare("DELETE FROM `phone_verification_service` WHERE `phone_number` = ?");
+ $stmtDel->execute([$encryptedPhone]);
+
+ $stmtIns = $con->prepare("
+ INSERT INTO `phone_verification_service`
+ (`phone_number`, `token_code`, `expiration_time`, `is_verified`, `created_at`)
+ VALUES (?, ?, ?, 0, NOW())
+ ");
+ $stmtIns->execute([
+ $encryptedPhone,
+ $encryptedOtp,
+ $expirationTime
+ ]);
+ } elseif ($user_type === 'driver') {
+ if ($context === 'token_change') {
+ // Delete old verification attempts
+ $stmtDel = $con->prepare("DELETE FROM `token_verification_driver` WHERE `phone_number` = ?");
+ $stmtDel->execute([$encryptedPhone]);
+
+ // Insert new attempt
+ $stmtIns = $con->prepare("
+ INSERT INTO `token_verification_driver`
+ (`phone_number`, `token`, `expiration_time`, `verified`, `created_at`)
+ VALUES (?, ?, ?, 0, NOW())
+ ");
+ $stmtIns->execute([
+ $encryptedPhone,
+ $encryptedOtp,
+ $expirationTime
+ ]);
+ } else {
+ // Delete old verification attempts
+ $stmtDel = $con->prepare("DELETE FROM `phone_verification` WHERE `phone_number` = ?");
+ $stmtDel->execute([$encryptedPhone]);
+
+ // Insert new attempt
+ $stmtIns = $con->prepare("
+ INSERT INTO `phone_verification`
+ (`phone_number`, `driverId`, `email`, `token_code`, `expiration_time`, `is_verified`, `created_at`)
+ VALUES (?, ?, ?, ?, ?, 0, NOW())
+ ");
+ $stmtIns->execute([
+ $encryptedPhone,
+ $driverId ?: '',
+ $encryptedEmail,
+ $encryptedOtp,
+ $expirationTime
+ ]);
+ }
+ } else {
+ if ($context === 'token_change') {
+ // Delete old verification attempts
+ $stmtDel = $con->prepare("DELETE FROM `token_verification` WHERE `phone_number` = ?");
+ $stmtDel->execute([$encryptedPhone]);
+
+ // Insert new attempt
+ $stmtIns = $con->prepare("
+ INSERT INTO `token_verification`
+ (`phone_number`, `token`, `expiration_time`, `verified`, `created_at`)
+ VALUES (?, ?, ?, 0, NOW())
+ ");
+ $stmtIns->execute([
+ $encryptedPhone,
+ $encryptedOtp,
+ $expirationTime
+ ]);
+ } else {
+ // Delete old verification attempts
+ $stmtDel = $con->prepare("DELETE FROM `phone_verification_passenger` WHERE `phone_number` = ?");
+ $stmtDel->execute([$encryptedPhone]);
+
+ // Insert new attempt
+ $stmtIns = $con->prepare("
+ INSERT INTO `phone_verification_passenger`
+ (`phone_number`, `token`, `expiration_time`, `verified`, `created_at`)
+ VALUES (?, ?, ?, 0, NOW())
+ ");
+ $stmtIns->execute([
+ $encryptedPhone,
+ $encryptedOtp,
+ $expirationTime
+ ]);
+ }
+ }
+
+ jsonSuccess(null, "OTP sent and saved successfully");
+ } catch (PDOException $e) {
+ error_log("⚠️ [OTP DB Save] Error: " . $e->getMessage());
+ jsonError("OTP sent but failed to save verification data");
+ }
+} else {
+ jsonError("Failed to send verification code. Please try again.");
+}
diff --git a/backend/auth/otp/verify.php b/backend/auth/otp/verify.php
new file mode 100644
index 0000000..3cb5930
--- /dev/null
+++ b/backend/auth/otp/verify.php
@@ -0,0 +1,222 @@
+enforce(RateLimiter::identifier(), 'otp');
+
+// 1. Fetch input parameters
+$phone_number = filterRequest("phone_number");
+if (empty($phone_number)) {
+ $phone_number = filterRequest("receiver");
+}
+
+$token_code = filterRequest("token_code");
+if (empty($token_code)) {
+ $token_code = filterRequest("token");
+}
+
+$user_type = filterRequest("user_type");
+$context = filterRequest("context"); // token_change | login (default)
+
+$authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? (function_exists('apache_request_headers') ? (apache_request_headers()['Authorization'] ?? null) : null);
+if (!empty($authHeader) && preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) {
+ $jwtToken = $matches[1];
+ $tokenParts = explode('.', $jwtToken);
+ if (count($tokenParts) === 3) {
+ $payload = json_decode(base64_decode($tokenParts[1]), true);
+ if (isset($payload['role'])) {
+ $user_type = $payload['role'];
+ }
+ }
+}
+
+if (empty($phone_number)) {
+ jsonError("Phone number is required.");
+ exit;
+}
+
+if (empty($token_code)) {
+ jsonError("Verification token code is required.");
+ exit;
+}
+
+if (empty($user_type)) {
+ if (strpos($_SERVER['REQUEST_URI'], 'driver') !== false) {
+ $user_type = 'driver';
+ } else {
+ $user_type = 'passenger';
+ }
+}
+
+if (empty($user_type) || !in_array($user_type, ['passenger', 'driver', 'admin', 'service'])) {
+ jsonError("User type must be 'passenger', 'driver', 'admin', or 'service'.");
+ exit;
+}
+
+// 2. Establish DB Connection
+try {
+ $con = Database::get('main');
+} catch (Exception $e) {
+ http_response_code(500);
+ exit(json_encode(['error' => 'Database connection failed']));
+}
+
+// 3. Encrypt data to query
+$encryptedPhone = $encryptionHelper->encryptData($phone_number);
+$encryptedToken = $encryptionHelper->encryptData($token_code);
+
+// 4. Verify based on user type
+try {
+ if ($user_type === 'admin') {
+ $sql = "SELECT * FROM token_verification_admin
+ WHERE phone_number = :phone AND token = :token
+ AND expiration_time >= NOW()";
+ $stmt = $con->prepare($sql);
+ $stmt->bindParam(':phone', $encryptedPhone, PDO::PARAM_STR);
+ $stmt->bindParam(':token', $encryptedToken, PDO::PARAM_STR);
+ $stmt->execute();
+
+ if ($stmt->rowCount() > 0) {
+ $deviceNumber = filterRequest("device_number") ?? '';
+ // adminUser stores unencrypted phone
+ $checkAdmin = $con->prepare("SELECT * FROM adminUser WHERE name = ?");
+ $checkAdmin->execute([$phone_number]);
+ $now = date("Y-m-d H:i:s");
+
+ if ($checkAdmin->rowCount() > 0) {
+ $update = $con->prepare("UPDATE adminUser SET device_number = ?, updated_at = ? WHERE name = ?");
+ $update->execute([$deviceNumber, $now, $phone_number]);
+ jsonSuccess(["message" => "verified and updated existing admin"]);
+ } else {
+ $insert = $con->prepare("INSERT INTO adminUser (device_number, name, created_at, updated_at) VALUES (?, ?, ?, ?)");
+ $insert->execute([$deviceNumber, $phone_number, $now, $now]);
+ jsonSuccess(["message" => "verified and new admin created"]);
+ }
+ } else {
+ jsonError("Your phone number could not be verified or the code is expired. Please try again.");
+ }
+ } elseif ($user_type === 'service') {
+ $sql = "SELECT `id` FROM `phone_verification_service`
+ WHERE `phone_number` = :phone AND `token_code` = :token
+ AND `expiration_time` > NOW()";
+ $stmt = $con->prepare($sql);
+ $stmt->bindParam(':phone', $encryptedPhone, PDO::PARAM_STR);
+ $stmt->bindParam(':token', $encryptedToken, PDO::PARAM_STR);
+ $stmt->execute();
+ $result = $stmt->fetch();
+
+ if ($result) {
+ $sqlUpdate = "UPDATE `phone_verification_service` SET `is_verified` = 1 WHERE `phone_number` = :phone";
+ $stmtUpd = $con->prepare($sqlUpdate);
+ $stmtUpd->bindParam(':phone', $encryptedPhone, PDO::PARAM_STR);
+ $stmtUpd->execute();
+ jsonSuccess(null, "Your phone number has been verified.");
+ } else {
+ jsonError("Your phone number could not be verified or the code is expired. Please try again.");
+ }
+ } elseif ($user_type === 'driver') {
+ if ($context === 'token_change') {
+ $sql = "SELECT `id` FROM `token_verification_driver`
+ WHERE `phone_number` = :phone
+ AND `token` = :token
+ AND `expiration_time` > NOW()";
+
+ $stmt = $con->prepare($sql);
+ $stmt->bindParam(':phone', $encryptedPhone, PDO::PARAM_STR);
+ $stmt->bindParam(':token', $encryptedToken, PDO::PARAM_STR);
+ $stmt->execute();
+ $result = $stmt->fetch();
+
+ if ($result) {
+ // Update driver verified status
+ $sqlUpdate = "UPDATE `token_verification_driver` SET `verified` = 1 WHERE `phone_number` = :phone";
+ $stmtUpd = $con->prepare($sqlUpdate);
+ $stmtUpd->bindParam(':phone', $encryptedPhone, PDO::PARAM_STR);
+ $stmtUpd->execute();
+
+ jsonSuccess(null, "Your phone number has been verified.");
+ } else {
+ jsonError("Your phone number could not be verified or the code is expired. Please try again.");
+ }
+ } else {
+ $sql = "SELECT `id` FROM `phone_verification`
+ WHERE `phone_number` = :phone
+ AND `token_code` = :token
+ AND `expiration_time` > NOW()";
+
+ $stmt = $con->prepare($sql);
+ $stmt->bindParam(':phone', $encryptedPhone, PDO::PARAM_STR);
+ $stmt->bindParam(':token', $encryptedToken, PDO::PARAM_STR);
+ $stmt->execute();
+ $result = $stmt->fetch();
+
+ if ($result) {
+ // Update driver is_verified status
+ $sqlUpdate = "UPDATE `phone_verification` SET `is_verified` = 1 WHERE `phone_number` = :phone";
+ $stmtUpd = $con->prepare($sqlUpdate);
+ $stmtUpd->bindParam(':phone', $encryptedPhone, PDO::PARAM_STR);
+ $stmtUpd->execute();
+
+ jsonSuccess(null, "Your phone number has been verified.");
+ } else {
+ jsonError("Your phone number could not be verified or the code is expired. Please try again.");
+ }
+ }
+ } else {
+ if ($context === 'token_change') {
+ $sql = "SELECT `id` FROM `token_verification`
+ WHERE `phone_number` = :phone
+ AND `token` = :token
+ AND `expiration_time` > NOW()";
+
+ $stmt = $con->prepare($sql);
+ $stmt->bindParam(':phone', $encryptedPhone, PDO::PARAM_STR);
+ $stmt->bindParam(':token', $encryptedToken, PDO::PARAM_STR);
+ $stmt->execute();
+ $result = $stmt->fetch();
+
+ if ($result) {
+ // Update passenger verified status
+ $sqlUpdate = "UPDATE `token_verification` SET `verified` = 1 WHERE `phone_number` = :phone";
+ $stmtUpd = $con->prepare($sqlUpdate);
+ $stmtUpd->bindParam(':phone', $encryptedPhone, PDO::PARAM_STR);
+ $stmtUpd->execute();
+
+ jsonSuccess(null, "Your phone number has been verified.");
+ } else {
+ jsonError("Your phone number could not be verified or the code is expired. Please try again.");
+ }
+ } else {
+ $sql = "SELECT `id` FROM `phone_verification_passenger`
+ WHERE `phone_number` = :phone
+ AND `token` = :token
+ AND `expiration_time` > NOW()";
+
+ $stmt = $con->prepare($sql);
+ $stmt->bindParam(':phone', $encryptedPhone, PDO::PARAM_STR);
+ $stmt->bindParam(':token', $encryptedToken, PDO::PARAM_STR);
+ $stmt->execute();
+ $result = $stmt->fetch();
+
+ if ($result) {
+ // Update passenger verified status
+ $sqlUpdate = "UPDATE `phone_verification_passenger` SET `verified` = 1 WHERE `phone_number` = :phone";
+ $stmtUpd = $con->prepare($sqlUpdate);
+ $stmtUpd->bindParam(':phone', $encryptedPhone, PDO::PARAM_STR);
+ $stmtUpd->execute();
+
+ jsonSuccess(null, "Your phone number has been verified.");
+ } else {
+ jsonError("Your phone number could not be verified or the code is expired. Please try again.");
+ }
+ }
+ }
+} catch (PDOException $e) {
+ error_log("⚠️ [OTP DB Verify] Error: " . $e->getMessage());
+ jsonError("An error occurred during verification. Please try again.");
+}
diff --git a/backend/auth/otpmessage.php b/backend/auth/otpmessage.php
deleted file mode 100755
index 9cef175..0000000
--- a/backend/auth/otpmessage.php
+++ /dev/null
@@ -1,88 +0,0 @@
- $username,
- 'password' => $password,
- 'language' => 'e' , // Assuming 'e' is for English as per original
- 'sender' => $sender,
- 'receiver' => $receiver,
- 'message' => $message
-];
-$jsonPayload = json_encode($payload);
-$response = callAPI("POST", $apiUrl, $jsonPayload);
-
-if ($response && isset($response->message) && $response->message == 'Success') {
- // 3. تخزين في Redis بدلاً من MySQL (أسرع وأكثر أماناً مع TTL تلقائي)
- if ($redis) {
- try {
- $redis->setex("otp:passenger:$receiver", 300, $otp); // صلاحية 5 دقائق
- jsonSuccess(null, "OTP sent and saved to Redis successfully");
- } catch (Exception $e) {
- error_log("Redis Error (OTP): " . $e->getMessage());
- jsonError("OTP sent but failed to save in Redis");
- }
- } else {
- jsonError("Redis service unavailable");
- }
-} else {
- jsonError("OTP not sent (SMS API failed or invalid response)");
-}
-
-// دالة الاتصال بالـ API
-function callAPI($method, $url, $data) {
-
- $curl = curl_init();
- curl_setopt_array($curl, [
- CURLOPT_URL => $url,
- CURLOPT_RETURNTRANSFER => true,
- CURLOPT_CUSTOMREQUEST => $method,
- CURLOPT_POSTFIELDS => $data,
- CURLOPT_HTTPHEADER => [
- "Content-Type: application/json",
- "Accept: application/json" // Often good to add
- ],
- CURLOPT_TIMEOUT => 30, // Set a timeout
- CURLOPT_CONNECTTIMEOUT => 10 // Set a connection timeout
- ]);
- $api_raw_response = curl_exec($curl);
-
- if (curl_errno($curl)) {
- $curl_error_msg = curl_error($curl);
- $curl_error_no = curl_errno($curl);
- error_log("cURL Error (callAPI): [{$curl_error_no}] " . $curl_error_msg);
- curl_close($curl);
- return false; // Indicate cURL failure clearly
- }
- curl_close($curl);
-
- $decoded_response = json_decode($api_raw_response);
- if (json_last_error() !== JSON_ERROR_NONE) {
- return null; // Indicate JSON decode failure
- }
- error_log("callAPI: Decoded response: " . print_r($decoded_response, true));
- return $decoded_response;
-}
-?>
\ No newline at end of file
diff --git a/backend/auth/passengerOTP/error_log b/backend/auth/passengerOTP/error_log
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/auth/passengerOTP/sendOtpPassenger.php b/backend/auth/passengerOTP/sendOtpPassenger.php
deleted file mode 100644
index fe74e40..0000000
--- a/backend/auth/passengerOTP/sendOtpPassenger.php
+++ /dev/null
@@ -1,42 +0,0 @@
-prepare($sql);
-$stmt->execute();
-
-$rowCount = $stmt->rowCount();
-
-if ($rowCount > 0) {
- // The phone number already exists, so update the data
- $sql = "UPDATE `phone_verification_passenger` SET `token_code` = '$token_code', `expiration_time` = DATE_ADD(NOW(), INTERVAL 5 MINUTE) WHERE `phone_number` = '$phone_number'";
- $stmt = $con->prepare($sql);
- $stmt->execute();
-
- if ($stmt->rowCount() > 0) {
- // The update was successful
- jsonSuccess($message = "Phone verification data updated successfully");
- } else {
- // The update was unsuccessful
- jsonError($message = "Failed to update phone verification data");
- }
-} else {
- // The phone number does not exist, so insert the data
- $sql = "INSERT INTO `phone_verification_passenger` (`phone_number`, `token_code`, `expiration_time`, `is_verified`, `created_at`) VALUES ('$phone_number', '$token_code', DATE_ADD(NOW(), INTERVAL 5 MINUTE), 0, NOW())";
- $stmt = $con->prepare($sql);
- $stmt->execute();
-
- if ($stmt->rowCount() > 0) {
- // The insertion was successful
- jsonSuccess($message = "Phone verification data saved successfully");
- } else {
- // The insertion was unsuccessful
- jsonError($message = "Failed to save phone verification data");
- }
-}
-?>
\ No newline at end of file
diff --git a/backend/auth/passengerOTP/verifyOtpPassenger.php b/backend/auth/passengerOTP/verifyOtpPassenger.php
deleted file mode 100644
index 12d953b..0000000
--- a/backend/auth/passengerOTP/verifyOtpPassenger.php
+++ /dev/null
@@ -1,28 +0,0 @@
-encryptData(filterRequest("phone_number"));
-$token_code = $encryptionHelper->encryptData(filterRequest("token"));
-
-// error_log("phone=$phone_number, token=$token_code");
-
-// Check if the phone number and token code match
-$sql = "SELECT * FROM `phone_verification_passenger` WHERE `phone_number` = '$phone_number' AND `token` = '$token_code'
-AND `verified` = 0 ";
-// error_log("sql is =$sql");
-
-$stmt = $con->prepare($sql);
-$stmt->execute();
-$result = $stmt->fetch();
-
-if ($result) {
- // $id = $result["id"];
- $sql = "UPDATE `phone_verification_passenger` SET `verified` = 1 WHERE `phone_number` = '$phone_number'";
- $stmt = $con->prepare($sql);
- $stmt->execute();
-
- jsonSuccess($message = "Your phone number has been verified.");
-} else {
- jsonError($message = "Your phone number could not be verified. Please try again.");
-}
-?>
\ No newline at end of file
diff --git a/backend/auth/resetPassword.php b/backend/auth/resetPassword.php
deleted file mode 100644
index e69de29..0000000
diff --git a/backend/auth/sendEmail.php b/backend/auth/sendEmail.php
old mode 100755
new mode 100644
index e18e13f..885e487
--- a/backend/auth/sendEmail.php
+++ b/backend/auth/sendEmail.php
@@ -4,7 +4,7 @@ require_once __DIR__ . '/../connect.php';
$email = filterRequest("email");
$token = filterRequest("token");
-$admin='support@sefer.live';
+$admin='support@siromove.com';
$headers = "MIME-Version: 1.0" . "\r\n";
$headers .= "Content-type: text/html; charset=UTF-8" . "\r\n";
$headers .= "From: $admin" . "\r\n";
@@ -18,7 +18,7 @@ $bodyEmail = "
Hi [$email],
-We recently received a request to verify your email address for your account on SEFER App.
+We recently received a request to verify your email address for your account on Siro App.
To verify your email address, please write this to app .
$token
@@ -26,7 +26,7 @@ $token
If you did not request to verify your email address, please ignore this email.
Thank you,
-SEFER Team.
+Siro Team.