Update: 2026-06-12 20:40:40
This commit is contained in:
676
auth_flow_admin_staff.md
Normal file
676
auth_flow_admin_staff.md
Normal file
@@ -0,0 +1,676 @@
|
|||||||
|
<div dir="rtl" lang="ar">
|
||||||
|
|
||||||
|
# سير عمل تسجيل الدخول في نظام 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
|
||||||
|
|
||||||
|
</div>
|
||||||
50
auth_flow_cleanup_report.md
Normal file
50
auth_flow_cleanup_report.md
Normal file
@@ -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).
|
||||||
0
backend/Admin/AdminCaptain/getDriversPhonesAndTokens.php
Executable file → Normal file
0
backend/Admin/AdminCaptain/getDriversPhonesAndTokens.php
Executable file → Normal file
54
backend/Admin/Staff/activate.php
Normal file
54
backend/Admin/Staff/activate.php
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Admin/Staff/activate.php
|
||||||
|
* تفعيل الحسابات المعلقة للمشرفين (Admins) وموظفي خدمة العملاء (Service) من قبل المشرف العام
|
||||||
|
*/
|
||||||
|
require_once __DIR__ . '/../../core/bootstrap.php';
|
||||||
|
require_once __DIR__ . '/../../functions.php';
|
||||||
|
|
||||||
|
$userId = filterRequest('user_id');
|
||||||
|
$type = filterRequest('type'); // 'admin' or 'service'
|
||||||
|
|
||||||
|
if (empty($userId) || empty($type)) {
|
||||||
|
jsonError("رقم المستخدم ونوع الحساب مطلوبان.");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$con = Database::get('main');
|
||||||
|
|
||||||
|
// التحقق من صلاحية المشرف العام (Super Admin أو Admin فقط)
|
||||||
|
$jwtService = new JwtService($redis);
|
||||||
|
$auth = $jwtService->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();
|
||||||
@@ -7,15 +7,14 @@ require_once __DIR__ . '/../../core/bootstrap.php';
|
|||||||
|
|
||||||
$con = Database::get('main');
|
$con = Database::get('main');
|
||||||
|
|
||||||
// التحقق من الصلاحيات: فقط المشرفين يمكنهم الإضافة
|
// التحقق من الصلاحيات: فقط المشرفين (super_admin أو admin) يمكنهم الإضافة
|
||||||
// إذا لم يكن هناك أي مدير في النظام، نسمح// تم تعطيل التحقق للسماح بإعادة التهيئة في مرحلة التطوير
|
$jwtService = new JwtService($redis);
|
||||||
// $count = $con->query("SELECT COUNT(*) FROM adminUser")->fetchColumn();
|
$auth = $jwtService->authenticate();
|
||||||
// if ($count > 0) die("Access Denied: Admin already initialized.");
|
$authRole = $auth->role ?? '';
|
||||||
// $auth = JwtService::authenticate($redis);
|
if ($authRole !== 'super_admin' && $authRole !== 'admin') {
|
||||||
// if ($auth['role'] !== 'super_admin' && $auth['role'] !== 'admin') {
|
jsonError("غير مصرح لك. فقط المشرفون يمكنهم إضافة موظفين.");
|
||||||
// jsonError("Unauthorized. Only Admins can add staff.");
|
exit;
|
||||||
// exit;
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
$name = filterRequest("name");
|
$name = filterRequest("name");
|
||||||
$phone = filterRequest("phone");
|
$phone = filterRequest("phone");
|
||||||
@@ -47,14 +46,20 @@ try {
|
|||||||
|
|
||||||
if ($role === 'admin') {
|
if ($role === 'admin') {
|
||||||
// الإضافة لجدول المديرين
|
// الإضافة لجدول المديرين
|
||||||
$sql = "INSERT INTO adminUser (id, fingerprint, fingerprint_hash, name, password, role, created_at)
|
// التأكد من وجود عمود phone في الجدول (كإجراء احترازي لتجنب الأخطاء إذا لم يكن موجوداً)
|
||||||
VALUES (:id, :fp, :fp_hash, :name, :pass, :role, NOW())";
|
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 = $con->prepare($sql);
|
||||||
$stmt->execute([
|
$stmt->execute([
|
||||||
':id' => $uniqueId,
|
':id' => $uniqueId,
|
||||||
':fp' => $encFp,
|
':fp' => $encFp,
|
||||||
':fp_hash' => $fpHash,
|
':fp_hash' => $fpHash,
|
||||||
':name' => $encName,
|
':name' => $encName,
|
||||||
|
':phone' => $encPhone,
|
||||||
':pass' => $hashedPassword,
|
':pass' => $hashedPassword,
|
||||||
':role' => $role
|
':role' => $role
|
||||||
]);
|
]);
|
||||||
|
|||||||
42
backend/Admin/Staff/pending.php
Normal file
42
backend/Admin/Staff/pending.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Admin/Staff/pending.php
|
||||||
|
* جلب الحسابات المعلقة للإداريين والخدمة
|
||||||
|
*/
|
||||||
|
require_once __DIR__ . '/../../core/bootstrap.php';
|
||||||
|
require_once __DIR__ . '/../../functions.php';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$con = Database::get('main');
|
||||||
|
|
||||||
|
// جلب الإداريين المعلقين
|
||||||
|
$stmt1 = $con->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();
|
||||||
0
backend/Admin/adminUser/add_invoice.php
Executable file → Normal file
0
backend/Admin/adminUser/add_invoice.php
Executable file → Normal file
0
backend/Admin/adminUser/invoice_total.php
Executable file → Normal file
0
backend/Admin/adminUser/invoice_total.php
Executable file → Normal file
33
backend/Admin/auth/login.php
Executable file → Normal file
33
backend/Admin/auth/login.php
Executable file → Normal file
@@ -8,6 +8,7 @@ require_once __DIR__ . '/../../functions.php';
|
|||||||
|
|
||||||
$fingerprint = filterRequest('fingerprint');
|
$fingerprint = filterRequest('fingerprint');
|
||||||
$password = filterRequest('password');
|
$password = filterRequest('password');
|
||||||
|
$phone = filterRequest('phone');
|
||||||
$audience = filterRequest('aud') ?? 'admin';
|
$audience = filterRequest('aud') ?? 'admin';
|
||||||
$isRenewal = filterRequest('is_renewal') === '1';
|
$isRenewal = filterRequest('is_renewal') === '1';
|
||||||
|
|
||||||
@@ -16,6 +17,10 @@ if (empty($fingerprint) || empty($password)) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rate Limiting: 5 محاولات في الدقيقة لكل IP
|
||||||
|
$rateLimiter = new RateLimiter($redis);
|
||||||
|
$rateLimiter->enforce(RateLimiter::identifier(), 'login');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$con = Database::get('main');
|
$con = Database::get('main');
|
||||||
|
|
||||||
@@ -25,6 +30,28 @@ try {
|
|||||||
$stmt->execute([':fp' => $fpHash]);
|
$stmt->execute([':fp' => $fpHash]);
|
||||||
$admin = $stmt->fetch(PDO::FETCH_ASSOC);
|
$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) {
|
if ($admin) {
|
||||||
// 1. التحقق من حالة الحساب
|
// 1. التحقق من حالة الحساب
|
||||||
if ($admin['status'] === 'pending') {
|
if ($admin['status'] === 'pending') {
|
||||||
@@ -69,8 +96,8 @@ try {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. توليد رمز تحقق OTP وإرساله عبر WhatsApp
|
// 3. توليد رمز تحقق OTP (6 أرقام) وإرساله عبر WhatsApp
|
||||||
$otp = rand(10000, 99999);
|
$otp = rand(100000, 999999);
|
||||||
$encryptedPhone = $admin['phone'] ?? '';
|
$encryptedPhone = $admin['phone'] ?? '';
|
||||||
|
|
||||||
if (empty($encryptedPhone)) {
|
if (empty($encryptedPhone)) {
|
||||||
@@ -112,7 +139,7 @@ try {
|
|||||||
jsonError("كلمة المرور غير صحيحة.");
|
jsonError("كلمة المرور غير صحيحة.");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
jsonError("الجهاز غير مسجل كمشرف.");
|
jsonError("الحساب أو الجهاز غير مسجل. يرجى إدخال رقم هاتفك وكلمة المرور إذا كان هذا أول تسجيل دخول لك.");
|
||||||
}
|
}
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
error_log("[Admin Login Error] " . $e->getMessage());
|
error_log("[Admin Login Error] " . $e->getMessage());
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Admin/auth/register.php
|
* Admin/auth/register.php
|
||||||
* التسجيل الذاتي للمشرفين - الحساب يكون بحالة pending بانتظار موافقة السوبر أدمن
|
* التسجيل الذاتي للمشرفين (Admins) مع التحقق من الصلاحيات من ملف .env
|
||||||
*/
|
*/
|
||||||
require_once __DIR__ . '/../../core/bootstrap.php';
|
require_once __DIR__ . '/../../core/bootstrap.php';
|
||||||
|
require_once __DIR__ . '/../../functions.php';
|
||||||
|
|
||||||
$name = filterRequest('name');
|
$name = filterRequest('name');
|
||||||
$phone = filterRequest('phone');
|
$phone = filterRequest('phone');
|
||||||
@@ -11,49 +12,75 @@ $password = filterRequest('password');
|
|||||||
$fingerprint = filterRequest('fingerprint');
|
$fingerprint = filterRequest('fingerprint');
|
||||||
|
|
||||||
if (empty($name) || empty($phone) || empty($password) || empty($fingerprint)) {
|
if (empty($name) || empty($phone) || empty($password) || empty($fingerprint)) {
|
||||||
jsonError("All fields are required.");
|
jsonError("جميع الحقول مطلوبة بما فيها بصمة الجهاز.");
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$con = Database::get('main');
|
// 1. التحقق من البيئة (Environment Whitelist)
|
||||||
|
$allowedPhonesStr = getenv('AUTHORIZED_ADMIN_PHONES');
|
||||||
// 1. التحقق من عدم وجود الحساب مسبقاً (عن طريق الهاتف أو البصمة)
|
if (!$allowedPhonesStr) {
|
||||||
$fpHash = hash('sha256', $fingerprint);
|
// في حال لم يتم إعداد المتغير، نرفض الجميع للأمان
|
||||||
$check = $con->prepare("SELECT id FROM adminUser WHERE phone = ? OR fingerprint_hash = ? LIMIT 1");
|
jsonError("غير مصرح لك بالتسجيل كمشرف (القائمة البيضاء غير معدة).");
|
||||||
$check->execute([$phone, $fpHash]);
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
if ($check->rowCount() > 0) {
|
$allowedPhones = array_map('trim', explode(',', $allowedPhonesStr));
|
||||||
jsonError("هذا الحساب أو الجهاز مسجل مسبقاً.");
|
if (!in_array($phone, $allowedPhones)) {
|
||||||
|
jsonError("أنت غير مصرح لك بالتسجيل كمشرف. يرجى مراجعة الإدارة.");
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. تجهيز البيانات
|
$con = Database::get('main');
|
||||||
$id = bin2hex(random_bytes(16));
|
|
||||||
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
|
|
||||||
$encName = $encryptionHelper->encryptData($name);
|
|
||||||
$encFp = $encryptionHelper->encryptData($fingerprint);
|
|
||||||
|
|
||||||
// 3. الإدخال في قاعدة البيانات (الحالة الافتراضية هي pending)
|
// 2. التحقق من عدم وجود الحساب مسبقاً (عن طريق الهاتف أو البصمة)
|
||||||
$sql = "INSERT INTO adminUser (id, name, phone, password, fingerprint, fingerprint_hash, role, status, created_at)
|
$fpHash = hash('sha256', $fingerprint);
|
||||||
VALUES (:id, :name, :phone, :pass, :fp, :fp_hash, 'admin', 'pending', NOW())";
|
$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 = $con->prepare($sql);
|
||||||
$stmt->execute([
|
$stmt->execute([
|
||||||
':id' => $id,
|
':id' => $uniqueId,
|
||||||
':name' => $encName,
|
|
||||||
':phone' => $phone,
|
|
||||||
':pass' => $hashedPassword,
|
|
||||||
':fp' => $encFp,
|
':fp' => $encFp,
|
||||||
':fp_hash' => $fpHash
|
':fp_hash' => $fpHash,
|
||||||
|
':name' => $encName,
|
||||||
|
':phone' => $encPhone,
|
||||||
|
':pass' => $hashedPassword
|
||||||
]);
|
]);
|
||||||
|
|
||||||
printSuccess([
|
printSuccess([
|
||||||
"status" => "pending",
|
"status" => "pending",
|
||||||
"message" => "تم تقديم طلب التسجيل بنجاح. يرجى انتظار موافقة الإدارة."
|
"message" => "تم تسجيل حسابك بنجاح وهو الآن قيد المراجعة. يرجى انتظار تفعيل المشرف العام."
|
||||||
]);
|
]);
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
error_log("[Admin Register Error] " . $e->getMessage());
|
error_log("[Admin Register Error] " . $e->getMessage());
|
||||||
jsonError("خطأ في السيرفر: " . $e->getMessage());
|
jsonError("خطأ في السيرفر: " . $e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exit();
|
||||||
|
|||||||
0
backend/Admin/auth/send_otp_admin.php
Executable file → Normal file
0
backend/Admin/auth/send_otp_admin.php
Executable file → Normal file
@@ -15,6 +15,10 @@ if (empty($otp) || empty($fingerprint)) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rate Limiting: 3 محاولات OTP في 5 دقائق لكل IP
|
||||||
|
$rateLimiter = new RateLimiter($redis);
|
||||||
|
$rateLimiter->enforce(RateLimiter::identifier(), 'otp');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$con = Database::get('main');
|
$con = Database::get('main');
|
||||||
|
|
||||||
|
|||||||
0
backend/Admin/auth/verify_otp_admin.php
Executable file → Normal file
0
backend/Admin/auth/verify_otp_admin.php
Executable file → Normal file
0
backend/Admin/driver/deleteCaptain.php
Executable file → Normal file
0
backend/Admin/driver/deleteCaptain.php
Executable file → Normal file
0
backend/Admin/driver/deleteRecord.php
Executable file → Normal file
0
backend/Admin/driver/deleteRecord.php
Executable file → Normal file
0
backend/Admin/driver/find_driver_by_phone.php
Executable file → Normal file
0
backend/Admin/driver/find_driver_by_phone.php
Executable file → Normal file
0
backend/Admin/driver/getBestDriver.php
Executable file → Normal file
0
backend/Admin/driver/getBestDriver.php
Executable file → Normal file
0
backend/Admin/driver/getDriverGiftPayment.php
Executable file → Normal file
0
backend/Admin/driver/getDriverGiftPayment.php
Executable file → Normal file
0
backend/Admin/driver/remove_from_blacklist.php
Executable file → Normal file
0
backend/Admin/driver/remove_from_blacklist.php
Executable file → Normal file
0
backend/Admin/driver/updateDriverFromAdmin.php
Executable file → Normal file
0
backend/Admin/driver/updateDriverFromAdmin.php
Executable file → Normal file
0
backend/Admin/employee/add.php
Executable file → Normal file
0
backend/Admin/employee/add.php
Executable file → Normal file
0
backend/Admin/employee/get.php
Executable file → Normal file
0
backend/Admin/employee/get.php
Executable file → Normal file
0
backend/Admin/error/error_list_last20.php
Executable file → Normal file
0
backend/Admin/error/error_list_last20.php
Executable file → Normal file
0
backend/Admin/error/error_search_by_phone.php
Executable file → Normal file
0
backend/Admin/error/error_search_by_phone.php
Executable file → Normal file
0
backend/Admin/errorApp.php
Executable file → Normal file
0
backend/Admin/errorApp.php
Executable file → Normal file
0
backend/Admin/facebook.php
Executable file → Normal file
0
backend/Admin/facebook.php
Executable file → Normal file
4
backend/Admin/jwtService.php
Executable file → Normal file
4
backend/Admin/jwtService.php
Executable file → Normal file
@@ -45,8 +45,8 @@ try {
|
|||||||
|
|
||||||
$startTime = microtime(true);
|
$startTime = microtime(true);
|
||||||
|
|
||||||
// دعم password_verify مع البقاء على التوافق مع كلمات السر القديمة (Plain Text)
|
// التحقق من كلمة المرور باستخدام password_hash فقط (الأمان)
|
||||||
if ($user && (password_verify($password, $user['password']) || $user['password'] === $password)) {
|
if ($user && password_verify($password, $user['password'])) {
|
||||||
|
|
||||||
$limiter->reset(RateLimiter::identifier(), 'login');
|
$limiter->reset(RateLimiter::identifier(), 'login');
|
||||||
|
|
||||||
|
|||||||
0
backend/Admin/passenger/admin_delete_and_blacklist_passenger.php
Executable file → Normal file
0
backend/Admin/passenger/admin_delete_and_blacklist_passenger.php
Executable file → Normal file
0
backend/Admin/passenger/admin_unblacklist.php
Executable file → Normal file
0
backend/Admin/passenger/admin_unblacklist.php
Executable file → Normal file
0
backend/Admin/passenger/admin_update_passenger.php
Executable file → Normal file
0
backend/Admin/passenger/admin_update_passenger.php
Executable file → Normal file
0
backend/Admin/rides/admin_get_rides_by_phone.php
Executable file → Normal file
0
backend/Admin/rides/admin_get_rides_by_phone.php
Executable file → Normal file
0
backend/Admin/rides/admin_update_ride_status.php
Executable file → Normal file
0
backend/Admin/rides/admin_update_ride_status.php
Executable file → Normal file
0
backend/Admin/rides/get_driver_live_pos.php
Executable file → Normal file
0
backend/Admin/rides/get_driver_live_pos.php
Executable file → Normal file
0
backend/Admin/rides/get_rides_by_status.php
Executable file → Normal file
0
backend/Admin/rides/get_rides_by_status.php
Executable file → Normal file
0
backend/Admin/rides/monitorRide.php
Executable file → Normal file
0
backend/Admin/rides/monitorRide.php
Executable file → Normal file
0
backend/Admin/send_whatsapp_message.php
Executable file → Normal file
0
backend/Admin/send_whatsapp_message.php
Executable file → Normal file
0
backend/Admin/view_errors.php
Executable file → Normal file
0
backend/Admin/view_errors.php
Executable file → Normal file
@@ -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
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/../../connect.php';
|
|
||||||
|
|
||||||
$sql = "
|
|
||||||
SELECT
|
|
||||||
`id`,
|
|
||||||
`phone`,
|
|
||||||
`email`,
|
|
||||||
`gender`,
|
|
||||||
`birthdate`,
|
|
||||||
`first_name`,
|
|
||||||
`last_name`,
|
|
||||||
`sosPhone`
|
|
||||||
FROM
|
|
||||||
`passengers`
|
|
||||||
";
|
|
||||||
|
|
||||||
$stmt = $con->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");
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
0
backend/auth/captin/loginFromGoogle.php
Executable file → Normal file
0
backend/auth/captin/loginFromGoogle.php
Executable file → Normal file
0
backend/auth/captin/loginUsingCredentialsWithoutGoogle.php
Executable file → Normal file
0
backend/auth/captin/loginUsingCredentialsWithoutGoogle.php
Executable file → Normal file
@@ -1,132 +0,0 @@
|
|||||||
<?php
|
|
||||||
$allowRegistration = true;
|
|
||||||
require_once __DIR__ . '/../../connect.php';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
try {
|
|
||||||
/* =========== 1) الحقول الواردة من الـ POST =========== */
|
|
||||||
$required = ["phone", "password", "first_name", "last_name"];
|
|
||||||
$optional = [
|
|
||||||
"id", "email", "gender", "license_type", "national_number",
|
|
||||||
"name_arabic", "issue_date", "expiry_date", "license_categories",
|
|
||||||
"address", "licenseIssueDate", "status", "birthdate", "site",
|
|
||||||
"accountBank", "bankCode", "employmentType",
|
|
||||||
"maritalStatus", "fullNameMaritial", "expirationDate"
|
|
||||||
];
|
|
||||||
|
|
||||||
$data = [];
|
|
||||||
|
|
||||||
// التحقق من الحقول المطلوبة
|
|
||||||
foreach ($required as $f) {
|
|
||||||
$val = filterRequest($f);
|
|
||||||
if ($val === null || $val === '') {
|
|
||||||
jsonError("Missing required field: $f");
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
$data[$f] = $val;
|
|
||||||
}
|
|
||||||
|
|
||||||
// قراءة الحقول الاختيارية
|
|
||||||
foreach ($optional as $f) {
|
|
||||||
$v = filterRequest($f);
|
|
||||||
$data[$f] = ($v === null || $v === '' || $v === 'Not specified') ? null : $v;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($data['email'] === null) {
|
|
||||||
// phone هنا ما زال خامًا (غير مُشفَّر)
|
|
||||||
$data['email'] = $data['phone'] . '@intaleqapp.com';
|
|
||||||
}
|
|
||||||
/* =========== 2) تشفير الحقول الحسّاسة =========== */
|
|
||||||
$encryptThese = ["phone", "email", "first_name", "last_name", "name_arabic","gender", "national_number",
|
|
||||||
"address", "site", "fullNameMaritial"];
|
|
||||||
|
|
||||||
foreach ($encryptThese as $f) {
|
|
||||||
if ($data[$f] !== null) {
|
|
||||||
$data[$f] = $encryptionHelper->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.");
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/../../connect.php';
|
|
||||||
|
|
||||||
// استرجاع البيانات من الطلب
|
|
||||||
$phone_number = filterRequest("phone_number");
|
|
||||||
$driverId = filterRequest("driverId");
|
|
||||||
$email = filterRequest("email");
|
|
||||||
$expiration_time = filterRequest("expiration_time"); // اختياري للمستقبل
|
|
||||||
|
|
||||||
// تحقق من وجود رقم الهاتف
|
|
||||||
if (empty($phone_number)) {
|
|
||||||
jsonError("Phone number is required");
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rate Limiting للحماية من هجمات استنزاف الرسائل
|
|
||||||
if (isset($redis)) {
|
|
||||||
$redisKey = "otp_limit:driver:$phone_number";
|
|
||||||
if ($redis->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;
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
0
backend/auth/captin/updateDriverClaim.php
Executable file → Normal file
0
backend/auth/captin/updateDriverClaim.php
Executable file → Normal file
@@ -1,56 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/../../connect.php';
|
|
||||||
|
|
||||||
$id = filterRequest("id");
|
|
||||||
|
|
||||||
// تحقق من وجود بيانات
|
|
||||||
if (empty($_POST)) {
|
|
||||||
jsonError("No passenger data provided for update.");
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// الحقول الحساسة التي يجب تشفيرها
|
|
||||||
$fieldsToEncrypt = ["phone", "email", "gender", "birthdate", "site", "first_name", "last_name", "sosPhone"];
|
|
||||||
|
|
||||||
// بناء الحقول والمعاملات
|
|
||||||
$columnValues = [];
|
|
||||||
$params = [];
|
|
||||||
|
|
||||||
foreach ($fieldsToEncrypt as $field) {
|
|
||||||
if (isset($_POST[$field])) {
|
|
||||||
$value = filterRequest($field);
|
|
||||||
$encryptedValue = $encryptionHelper->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());
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
0
backend/auth/captin/updateShamCashDriver.php
Executable file → Normal file
0
backend/auth/captin/updateShamCashDriver.php
Executable file → Normal file
@@ -1,39 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/../../connect.php';
|
|
||||||
|
|
||||||
$phone_number = filterRequest("phone_number");
|
|
||||||
$token_code = filterRequest("token_code");
|
|
||||||
|
|
||||||
$encryptedPhone = $encryptionHelper->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.");
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
0
backend/auth/checkPhoneNumberISVerfiedPassenger.php
Executable file → Normal file
0
backend/auth/checkPhoneNumberISVerfiedPassenger.php
Executable file → Normal file
@@ -1,23 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/../connect.php';
|
|
||||||
|
|
||||||
// Import the map
|
|
||||||
$cn = array(
|
|
||||||
"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;
|
|
||||||
?>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
["3","7","1","9","0","5","2","6","4","8"]
|
|
||||||
0
backend/auth/document_syria/ai_document.php
Executable file → Normal file
0
backend/auth/document_syria/ai_document.php
Executable file → Normal file
@@ -1,115 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* upload_document.php
|
|
||||||
* الغرض: رفع صورة وثيقة فقط وإرجاع رابطها (بدون ذكاء صناعي)
|
|
||||||
*/
|
|
||||||
|
|
||||||
require_once __DIR__ . '/../../connect.php';
|
|
||||||
|
|
||||||
uploadLog("🚀 [uploadDocSyria.php] Document upload script started.");
|
|
||||||
|
|
||||||
$driverId = trim((string) filterRequest("driver_id"));
|
|
||||||
$type = trim((string) filterRequest("type"));
|
|
||||||
|
|
||||||
// ✅ التحقق من الحقول الاختيارية
|
|
||||||
if ($driverId === "") { $driverId = "unknown"; }
|
|
||||||
if ($type === "") { $type = "generic"; }
|
|
||||||
|
|
||||||
uploadLog("📥 Request parameters: driver_id=$driverId, type=$type");
|
|
||||||
|
|
||||||
// ✅ التحقق من ملف الصورة
|
|
||||||
if (isset($_FILES['image'])) {
|
|
||||||
uploadLog("$_FILES['image'] metadata", 'INFO', [
|
|
||||||
'name' => $_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,
|
|
||||||
]);
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
<?php
|
|
||||||
// File: callback.php
|
|
||||||
// هذا الملف يستقبل الرد من جوجل بعد تسجيل المستخدم دخوله.
|
|
||||||
// يقوم بتحديث حالة الجلسة بالبيانات الصحيحة.
|
|
||||||
|
|
||||||
// 1. الإعدادات
|
|
||||||
$clientID = '1086900987150-j8brn0i5s97315kh1ej9jr72grkfqgh5.apps.googleusercontent.com';
|
|
||||||
$clientSecret = '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';
|
|
||||||
$redirectUri = 'https://api.tripz-egypt.com/tripz/auth/google_auth/callback.php';
|
|
||||||
|
|
||||||
// 2. التحقق من وجود 'code' و 'state' من جوجل
|
|
||||||
if (!isset($_GET['code']) || !isset($_GET['state'])) {
|
|
||||||
die('Invalid callback request.');
|
|
||||||
}
|
|
||||||
$authCode = $_GET['code'];
|
|
||||||
$loginToken = basename($_GET['state']); // الحماية من Path Traversal
|
|
||||||
|
|
||||||
$pollDir = __DIR__ . '/polls';
|
|
||||||
$sessionFile = $pollDir . '/' . $loginToken . '.json';
|
|
||||||
|
|
||||||
// التحقق من أن ملف الجلسة موجود
|
|
||||||
if (!file_exists($sessionFile)) {
|
|
||||||
die('Invalid or expired session.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. تبديل الـ code بـ access token
|
|
||||||
$tokenUrl = 'https://oauth2.googleapis.com/token';
|
|
||||||
$postData = [
|
|
||||||
'code' => $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 '<!DOCTYPE html><html><head><title>Success</title><meta name="viewport" content="width=device-width, initial-scale=1.0"></head><body style="font-family: sans-serif; text-align: center; padding-top: 50px;"><h1>Authentication Successful</h1><p>You can now return to the Tripz app.</p></body></html>';
|
|
||||||
exit();
|
|
||||||
?>
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
<?php
|
|
||||||
// File: check_status.php
|
|
||||||
// هذا الملف الذي سيقوم التطبيق بالاتصال به بشكل دوري.
|
|
||||||
// يتحقق من حالة الجلسة ويرجع البيانات عند نجاحها.
|
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
|
|
||||||
// 1. استقبال الـ loginToken من التطبيق
|
|
||||||
$input = json_decode(file_get_contents('php://input'), true);
|
|
||||||
if (!isset($input['loginToken'])) {
|
|
||||||
http_response_code(400);
|
|
||||||
echo json_encode(['status' => '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();
|
|
||||||
?>
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
<?php
|
|
||||||
$redirectUri = 'https://api.tripz-egypt.com/tripz/auth/google_auth/callback.php';
|
|
||||||
|
|
||||||
// google_auth.php
|
|
||||||
|
|
||||||
require_once __DIR__ . '/../vendor/autoload.php';
|
|
||||||
|
|
||||||
use Google\Client;
|
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
|
|
||||||
$response = [
|
|
||||||
'success' => 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);
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
<?php
|
|
||||||
// File: start_login.php
|
|
||||||
// هذا الملف يبدأ عملية تسجيل الدخول.
|
|
||||||
// يقوم بإنشاء معرف فريد ورابط تسجيل الدخول وإرسالهم للتطبيق.
|
|
||||||
|
|
||||||
// 1. الإعدادات
|
|
||||||
$clientID = '1086900987150-j8brn0i5s97315kh1ej9jr72grkfqgh5.apps.googleusercontent.com';
|
|
||||||
$redirectUri = 'https://api.tripz-egypt.com/tripz/auth/google_auth/callback.php';
|
|
||||||
$scopes = 'email profile';
|
|
||||||
|
|
||||||
// 2. إنشاء معرف فريد للجلسة (login token)
|
|
||||||
$loginToken = bin2hex(random_bytes(24));
|
|
||||||
|
|
||||||
// 3. إنشاء مجلد لتخزين الجلسات المؤقتة إذا لم يكن موجوداً
|
|
||||||
$pollDir = __DIR__ . '/polls';
|
|
||||||
if (!is_dir($pollDir)) {
|
|
||||||
mkdir($pollDir, 0775, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. إنشاء ملف مؤقت لهذه الجلسة
|
|
||||||
$sessionFile = $pollDir . '/' . $loginToken . '.json';
|
|
||||||
file_put_contents($sessionFile, json_encode(['status' => '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();
|
|
||||||
?>
|
|
||||||
0
backend/auth/loginFromGooglePassenger.php
Executable file → Normal file
0
backend/auth/loginFromGooglePassenger.php
Executable file → Normal file
229
backend/auth/otp/providers.php
Normal file
229
backend/auth/otp/providers.php
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
<?php
|
||||||
|
// File: backend/auth/otp/providers.php
|
||||||
|
// Encapsulates external OTP gateway API calls for Kazumi, Intaleq, and Nabeh.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send SMS OTP via Kazumi SMS Gateway (Egypt)
|
||||||
|
*
|
||||||
|
* @param string $receiver Recipient phone number (e.g. +2010xxxxxxxx)
|
||||||
|
* @param string $otp 3-digit verification code
|
||||||
|
* @return bool True if OTP was sent successfully
|
||||||
|
*/
|
||||||
|
function sendKazumiSms(string $receiver, string $otp): bool {
|
||||||
|
$username = getenv('SMS_USERNAME');
|
||||||
|
$password = getenv('SMS_PASSWORD_EGYPT');
|
||||||
|
$sender = getenv('SMS_SENDER');
|
||||||
|
|
||||||
|
if (!$username || !$password || !$sender) {
|
||||||
|
error_log("⚠️ [Kazumi OTP] Missing credentials in environment variables.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$message = "Siro app code is " . $otp;
|
||||||
|
$apiUrl = 'https://sms.kazumi.me/api/sms/send-sms';
|
||||||
|
|
||||||
|
$payload = [
|
||||||
|
'username' => $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;
|
||||||
|
}
|
||||||
260
backend/auth/otp/request.php
Normal file
260
backend/auth/otp/request.php
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
<?php
|
||||||
|
// File: backend/auth/otp/request.php
|
||||||
|
// Unified OTP request endpoint with geographical routing (Syria, Egypt, Jordan)
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../../core/bootstrap.php';
|
||||||
|
require_once __DIR__ . '/../../functions.php';
|
||||||
|
require_once __DIR__ . '/providers.php';
|
||||||
|
|
||||||
|
// 1. Rate Limiting check (max 3 requests per 5 minutes per IP)
|
||||||
|
$limiter = new RateLimiter($redis);
|
||||||
|
$limiter->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.");
|
||||||
|
}
|
||||||
222
backend/auth/otp/verify.php
Normal file
222
backend/auth/otp/verify.php
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
<?php
|
||||||
|
// File: backend/auth/otp/verify.php
|
||||||
|
// Unified OTP verification endpoint
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../../core/bootstrap.php';
|
||||||
|
require_once __DIR__ . '/../../functions.php';
|
||||||
|
|
||||||
|
// 0. Rate Limiting: 3 محاولات OTP كل 5 دقائق لكل IP
|
||||||
|
$rateLimiter = new RateLimiter($redis);
|
||||||
|
$rateLimiter->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.");
|
||||||
|
}
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
require_once __DIR__ . '/../connect.php'; // Contains DB connection, filterRequest, printSuccess/Failure, encryptionHelper
|
|
||||||
|
|
||||||
$receiver = filterRequest("phone_number"); // رقم الهاتف
|
|
||||||
|
|
||||||
if (empty($receiver)) {
|
|
||||||
jsonError("Receiver phone number is required.");
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$username = getenv('SMS_USERNAME');
|
|
||||||
$password = getenv('SMS_PASSWORD_EGYPT'); // Make sure this is the correct variable name for Egypt
|
|
||||||
$sender = getenv('SMS_SENDER');
|
|
||||||
|
|
||||||
|
|
||||||
if (!$username || !$password || !$sender) {
|
|
||||||
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$otp = rand(10000, 99999);
|
|
||||||
$message = "Tripz app code is " . $otp;
|
|
||||||
|
|
||||||
$apiUrl = 'https://sms.kazumi.me/api/sms/send-sms';
|
|
||||||
$payload = [
|
|
||||||
'username' => $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;
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/../../connect.php';
|
|
||||||
|
|
||||||
$phone_number = filterRequest("phone_number");
|
|
||||||
$token_code = filterRequest("token_code");
|
|
||||||
$expiration_time = filterRequest("expiration_time"); // Assuming this is a timestamp
|
|
||||||
|
|
||||||
// Check if the phone number already exists
|
|
||||||
$sql = "SELECT * FROM `phone_verification_passenger` WHERE `phone_number` = '$phone_number'";
|
|
||||||
$stmt = $con->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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/../../connect.php';
|
|
||||||
|
|
||||||
$phone_number = $encryptionHelper->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.");
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
6
backend/auth/sendEmail.php
Executable file → Normal file
6
backend/auth/sendEmail.php
Executable file → Normal file
@@ -4,7 +4,7 @@ require_once __DIR__ . '/../connect.php';
|
|||||||
$email = filterRequest("email");
|
$email = filterRequest("email");
|
||||||
$token = filterRequest("token");
|
$token = filterRequest("token");
|
||||||
|
|
||||||
$admin='support@sefer.live';
|
$admin='support@siromove.com';
|
||||||
$headers = "MIME-Version: 1.0" . "\r\n";
|
$headers = "MIME-Version: 1.0" . "\r\n";
|
||||||
$headers .= "Content-type: text/html; charset=UTF-8" . "\r\n";
|
$headers .= "Content-type: text/html; charset=UTF-8" . "\r\n";
|
||||||
$headers .= "From: $admin" . "\r\n";
|
$headers .= "From: $admin" . "\r\n";
|
||||||
@@ -18,7 +18,7 @@ $bodyEmail = "
|
|||||||
<body>
|
<body>
|
||||||
<p>Hi [$email],</p>
|
<p>Hi [$email],</p>
|
||||||
|
|
||||||
<p>We recently received a request to verify your email address for your account on SEFER App.</p>
|
<p>We recently received a request to verify your email address for your account on Siro App.</p>
|
||||||
|
|
||||||
<p>To verify your email address, please write this to app .</p>
|
<p>To verify your email address, please write this to app .</p>
|
||||||
$token
|
$token
|
||||||
@@ -26,7 +26,7 @@ $token
|
|||||||
<p>If you did not request to verify your email address, please ignore this email.</p>
|
<p>If you did not request to verify your email address, please ignore this email.</p>
|
||||||
|
|
||||||
<p>Thank you,</p>
|
<p>Thank you,</p>
|
||||||
SEFER Team.
|
Siro Team.
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
";
|
";
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
require_once __DIR__ . '/../../connect.php';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$sql = "SELECT
|
|
||||||
*
|
|
||||||
FROM
|
|
||||||
`smsSender`
|
|
||||||
WHERE
|
|
||||||
id = '1'";
|
|
||||||
|
|
||||||
|
|
||||||
$stmt = $con->prepare($sql);
|
|
||||||
$stmt->execute();
|
|
||||||
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
if ($stmt->rowCount() > 0) {
|
|
||||||
|
|
||||||
jsonSuccess($data = $result);
|
|
||||||
} else {
|
|
||||||
|
|
||||||
|
|
||||||
jsonError($message = "No driver order data found");
|
|
||||||
}
|
|
||||||
|
|
||||||
?>
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/../../connect.php';
|
|
||||||
|
|
||||||
// استقبال رقم الهاتف
|
|
||||||
$phone = filterRequest('phone');
|
|
||||||
$language = filterRequest('lang') ?? 'r';
|
|
||||||
|
|
||||||
// 1️⃣ جلب بيانات API من البيئة
|
|
||||||
$username = "Sefer";
|
|
||||||
$password = getenv("SMS_PASSWORD_EGYPT");
|
|
||||||
$apiEndpoint = getenv("SMS_API_ENDPOINT");
|
|
||||||
$sender = "SEFER";
|
|
||||||
$appName = "Tripz";
|
|
||||||
|
|
||||||
if (!$password || !$apiEndpoint) {
|
|
||||||
jsonError("API configuration is missing");
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2️⃣ توليد كود OTP من السيرفر
|
|
||||||
$otp = rand(100000, 999999);
|
|
||||||
|
|
||||||
// 3️⃣ تشفير البيانات قبل تخزينها
|
|
||||||
$phoneEncrypted = $encryptionHelper->encryptData($phone);
|
|
||||||
$otpEncrypted = $encryptionHelper->encryptData($otp);
|
|
||||||
|
|
||||||
// 4️⃣ تخزين OTP في قاعدة البيانات
|
|
||||||
try {
|
|
||||||
$insertOtp = "INSERT INTO otp_verification_fingerPrint (phone, otp) VALUES (?, ?)";
|
|
||||||
$stmt = $con->prepare($insertOtp);
|
|
||||||
$stmt->execute([$phoneEncrypted, $otpEncrypted]);
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
error_log("DB Insert Error: " . $e->getMessage());
|
|
||||||
jsonError("Failed to save OTP to the database");
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5️⃣ إرسال الرسالة عبر API
|
|
||||||
$message = "$appName app code is $otp\ncopy it to app";
|
|
||||||
|
|
||||||
$payload = json_encode([
|
|
||||||
"username" => $username,
|
|
||||||
"password" => $password,
|
|
||||||
"message" => $message,
|
|
||||||
"language" => $language,
|
|
||||||
"sender" => $sender,
|
|
||||||
"receiver" => $phone
|
|
||||||
]);
|
|
||||||
|
|
||||||
$ch = curl_init($apiEndpoint);
|
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
||||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
|
|
||||||
curl_setopt($ch, CURLOPT_POST, true);
|
|
||||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
|
||||||
$response = curl_exec($ch);
|
|
||||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
||||||
curl_close($ch);
|
|
||||||
|
|
||||||
// 6️⃣ التحقق من نجاح الإرسال
|
|
||||||
if ($httpCode != 200) {
|
|
||||||
error_log("SMS API Failed. HTTP Code: $httpCode. Response: " . $response);
|
|
||||||
jsonError("Failed to send OTP SMS");
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 7️⃣ إرجاع النتيجة
|
|
||||||
jsonSuccess(["message" => "OTP sent successfully"]);
|
|
||||||
?>
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
// Include the database connection file
|
|
||||||
require_once __DIR__ . '/../../connect.php';
|
|
||||||
|
|
||||||
// Filter and encrypt the phone number input
|
|
||||||
$phone_number = filterRequest("phone_number");
|
|
||||||
$phone_number = $encryptionHelper->encryptData($phone_number);
|
|
||||||
|
|
||||||
// Prepare the SQL query to verify the phone
|
|
||||||
$sql = "UPDATE phone_verification SET is_verified = 1 WHERE phone_number = :phone_number";
|
|
||||||
|
|
||||||
// Prepare the statement
|
|
||||||
$stmt = $con->prepare($sql);
|
|
||||||
$stmt->bindParam(":phone_number", $phone_number);
|
|
||||||
|
|
||||||
// Execute the query
|
|
||||||
$stmt->execute();
|
|
||||||
$affectedRows = $stmt->rowCount();
|
|
||||||
|
|
||||||
// Check if the update was successful
|
|
||||||
if ($affectedRows > 0) {
|
|
||||||
jsonSuccess(["message" => "Phone number verified successfully"]);
|
|
||||||
} else {
|
|
||||||
jsonError("No phone number found or verification failed");
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
require_once __DIR__ . '/../../connect.php';
|
|
||||||
|
|
||||||
// استقبال وتشفير رقم الهاتف
|
|
||||||
$phone_number = filterRequest("phone_number");
|
|
||||||
$phone_number = $encryptionHelper->encryptData($phone_number);
|
|
||||||
|
|
||||||
// تنفيذ الاستعلام
|
|
||||||
$sql = "UPDATE phone_verification_passenger SET verified = 1 WHERE phone_number = :phone_number";
|
|
||||||
$stmt = $con->prepare($sql);
|
|
||||||
$stmt->bindParam(":phone_number", $phone_number);
|
|
||||||
$stmt->execute();
|
|
||||||
|
|
||||||
$affectedRows = $stmt->rowCount();
|
|
||||||
|
|
||||||
// إرجاع النتيجة
|
|
||||||
if ($affectedRows > 0) {
|
|
||||||
jsonSuccess(["message" => "Phone number verified successfully"]);
|
|
||||||
} else {
|
|
||||||
jsonError("No phone number found or verification failed");
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
// تضمين ملف الاتصال بقاعدة البيانات والدوال المساعدة
|
|
||||||
require_once __DIR__ . '/../../connect.php';
|
|
||||||
// include "functions.php"; // افترض أن دالة filterRequest موجودة هنا
|
|
||||||
|
|
||||||
|
|
||||||
// --- بداية التعديل: استخدام واجهة RaseelPlus API ---
|
|
||||||
|
|
||||||
// توليد رمز تحقق عشوائي مكون من 5 أرقام
|
|
||||||
$otp = rand(10000, 99999);
|
|
||||||
|
|
||||||
// استقبال رقم الهاتف من الطلب
|
|
||||||
// تأكد من أن دالة filterRequest تقوم بتنقية المدخلات بشكل آمن
|
|
||||||
$receiver = filterRequest("receiver");
|
|
||||||
|
|
||||||
// رسالة الـ OTP. يمكنك تخصيصها حسب الحاجة
|
|
||||||
// تذكر أن التطبيق اسمه Tripz-egypt.com
|
|
||||||
$messageBody = "Your verification code for Tripz is: " . $otp;
|
|
||||||
|
|
||||||
// عنوان API الجديد
|
|
||||||
$apiUrl = 'https://raseelplus.com/api/send';
|
|
||||||
|
|
||||||
// بيانات الطلب (Payload) الجديدة لتتوافق مع RaseelPlus
|
|
||||||
$payload = [
|
|
||||||
"number" => $receiver, // رقم المستلم
|
|
||||||
"type" => "text",
|
|
||||||
"message" => $messageBody,
|
|
||||||
"instance_id" => "6863C59A7AFBD", // المعرف المأخوذ من مثال cURL
|
|
||||||
"access_token"=> "68617b9b8fe53" // مفتاح الوصول المأخوذ من مثال cURL
|
|
||||||
];
|
|
||||||
|
|
||||||
error_log("Sending OTP to $receiver via RaseelPlus. Message: $messageBody");
|
|
||||||
|
|
||||||
// استدعاء الـ API
|
|
||||||
$response = callAPI("POST", $apiUrl, json_encode($payload));
|
|
||||||
|
|
||||||
error_log("RaseelPlus API Response: " . print_r($response, true));
|
|
||||||
|
|
||||||
// --- نهاية التعديل ---
|
|
||||||
|
|
||||||
|
|
||||||
// التحقق من الاستجابة من الـ API
|
|
||||||
// ملاحظة: قد تحتاج إلى تعديل هذا الشرط بناءً على شكل الاستجابة الفعلي من RaseelPlus
|
|
||||||
// نفترض هنا أن الاستجابة الناجحة تحتوي على "status":"success" أو شيء مشابه
|
|
||||||
if ($response && !isset($response->error) && (isset($response->status) && $response->status == 'success' || isset($response->message))) {
|
|
||||||
|
|
||||||
// تحديد وقت انتهاء صلاحية الرمز (بعد 5 دقائق)
|
|
||||||
$expiration_time = date('Y-m-d H:i:s', strtotime('+5 minutes'));
|
|
||||||
$created_at = date('Y-m-d H:i:s');
|
|
||||||
|
|
||||||
error_log("API call successful. Saving to DB: phone=$receiver, token=$otp, expires=$expiration_time");
|
|
||||||
|
|
||||||
try {
|
|
||||||
// تشفير البيانات قبل حفظها (ممارسة أمنية جيدة)
|
|
||||||
// $receiver_encrypted = $encryptionHelper->encryptData($receiver);
|
|
||||||
// $otp_encrypted = $encryptionHelper->encryptData($otp);
|
|
||||||
|
|
||||||
// استخدام البيانات غير المشفرة مؤقتاً إذا لم تكن تستخدم التشفير حالياً
|
|
||||||
$receiver_to_db = $receiver;
|
|
||||||
$otp_to_db = $otp;
|
|
||||||
|
|
||||||
$stmt = $con->prepare("
|
|
||||||
INSERT INTO phone_verification_passenger
|
|
||||||
(phone_number, token, expiration_time, verified, created_at)
|
|
||||||
VALUES (?, ?, ?, 0, ?)
|
|
||||||
");
|
|
||||||
$success = $stmt->execute([$receiver_to_db, $otp_to_db, $expiration_time, $created_at]);
|
|
||||||
|
|
||||||
if ($success) {
|
|
||||||
error_log("OTP saved successfully to DB.");
|
|
||||||
// jsonSuccess() هي دالة مخصصة لديك لطباعة استجابة نجاح
|
|
||||||
jsonSuccess(null, 'OTP sent and saved successfully');
|
|
||||||
} else {
|
|
||||||
error_log("SQL execution failed.");
|
|
||||||
// jsonError() هي دالة مخصصة لديك لطباعة استجابة فشل
|
|
||||||
jsonError('OTP sent but failed to save to database');
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
error_log("Database Error: " . $e->getMessage());
|
|
||||||
jsonError('Database error occurred');
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// فشل إرسال الـ OTP
|
|
||||||
$errorMessage = isset($response->message) ? $response->message : "Unknown error";
|
|
||||||
error_log("Failed to send OTP. API response: " . $errorMessage);
|
|
||||||
jsonError('Failed to send OTP: ' . $errorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* دالة لإجراء استدعاءات API باستخدام cURL
|
|
||||||
* @param string $method نوع الطلب (e.g., "POST", "GET")
|
|
||||||
* @param string $url عنوان URL للـ API
|
|
||||||
* @param mixed $data البيانات المراد إرسالها
|
|
||||||
* @return mixed الاستجابة من الـ API بعد فك تشفير JSON
|
|
||||||
*/
|
|
||||||
function callAPI($method, $url, $data)
|
|
||||||
{
|
|
||||||
$curl = curl_init();
|
|
||||||
|
|
||||||
curl_setopt_array($curl, [
|
|
||||||
CURLOPT_URL => $url,
|
|
||||||
CURLOPT_RETURNTRANSFER => true, // إرجاع الاستجابة كنص بدلاً من طباعتها
|
|
||||||
CURLOPT_ENCODING => "",
|
|
||||||
CURLOPT_MAXREDIRS => 10,
|
|
||||||
CURLOPT_TIMEOUT => 30, // مهلة زمنية للطلب
|
|
||||||
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
|
|
||||||
CURLOPT_CUSTOMREQUEST => $method,
|
|
||||||
CURLOPT_POSTFIELDS => $data,
|
|
||||||
CURLOPT_HTTPHEADER => [
|
|
||||||
"Content-Type: application/json",
|
|
||||||
"Accept: application/json"
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$response = curl_exec($curl);
|
|
||||||
$err = curl_error($curl);
|
|
||||||
|
|
||||||
curl_close($curl);
|
|
||||||
|
|
||||||
if ($err) {
|
|
||||||
error_log("cURL Error #: " . $err);
|
|
||||||
return null; // إرجاع null في حالة وجود خطأ في cURL
|
|
||||||
} else {
|
|
||||||
return json_decode($response); // فك تشفير استجابة JSON
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// مثال على دالة طباعة النجاح (ضعها في ملف functions.php)
|
|
||||||
|
|
||||||
|
|
||||||
?>
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
require_once __DIR__ . '/../../connect.php';
|
|
||||||
|
|
||||||
|
|
||||||
$text='444';
|
|
||||||
|
|
||||||
$encryptedText = $encryptionHelper->encryptData($text);
|
|
||||||
|
|
||||||
$username = getenv('SMS_USERNAME');
|
|
||||||
$password = getenv('SMS_PASSWORD_EGYPT');
|
|
||||||
$sender = getenv('SMS_SENDER');
|
|
||||||
|
|
||||||
$language = filterRequest("language");
|
|
||||||
$receiver = filterRequest("receiver");
|
|
||||||
|
|
||||||
// Rate Limiting للحماية من هجمات استنزاف الرسائل
|
|
||||||
if (isset($redis) && !empty($receiver)) {
|
|
||||||
$redisKey = "otp_limit:passenger:$receiver";
|
|
||||||
if ($redis->exists($redisKey)) {
|
|
||||||
jsonError("Please wait before requesting a new OTP.");
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
$redis->setex($redisKey, 60, "1"); // حظر لمدة 60 ثانية
|
|
||||||
}
|
|
||||||
|
|
||||||
$otp = rand(10000, 99999);
|
|
||||||
$message0 = "Tripz app code is " . $otp;
|
|
||||||
|
|
||||||
$apiUrl = 'https://sms.kazumi.me/api/sms/send-sms';
|
|
||||||
|
|
||||||
$payload = [
|
|
||||||
'username' => $username,
|
|
||||||
'password' => $password,
|
|
||||||
'language' => $language,
|
|
||||||
'sender' => $sender,
|
|
||||||
'receiver' => $receiver,
|
|
||||||
'message' => $message0
|
|
||||||
];
|
|
||||||
|
|
||||||
error_log("Sending SMS to $receiver with OTP: $otp");
|
|
||||||
|
|
||||||
$response = callAPI("POST", $apiUrl, json_encode($payload));
|
|
||||||
|
|
||||||
error_log("API Response: " . print_r($response, true));
|
|
||||||
|
|
||||||
// التحقق من رسالة الاستجابة
|
|
||||||
if ($response && isset($response->message) && $response->message == "Success") {
|
|
||||||
$expiration_time = date('Y-m-d H:i:s', strtotime('+5 minutes'));
|
|
||||||
$created_at = date('Y-m-d H:i:s');
|
|
||||||
|
|
||||||
error_log("Saving to DB: phone=$receiver, token=$otp, expires=$expiration_time");
|
|
||||||
|
|
||||||
try {
|
|
||||||
$receiver1=$encryptionHelper->encryptData($receiver);
|
|
||||||
$otp1=$encryptionHelper->encryptData($otp);
|
|
||||||
|
|
||||||
$stmt = $con->prepare("
|
|
||||||
INSERT INTO phone_verification_passenger
|
|
||||||
(phone_number, token, expiration_time, verified, created_at)
|
|
||||||
VALUES (?, ?, ?, 0, ?)
|
|
||||||
");
|
|
||||||
$success = $stmt->execute([$receiver1, $otp1, $expiration_time, $created_at]);
|
|
||||||
|
|
||||||
if ($success) {
|
|
||||||
error_log("OTP saved successfully to DB.");
|
|
||||||
jsonSuccess(null, 'OTP sent and saved successfully');
|
|
||||||
} else {
|
|
||||||
error_log("SQL execution failed.");
|
|
||||||
jsonError('OTP sent but not saved to database');
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
error_log("Database Error: " . $e->getMessage());
|
|
||||||
jsonError('Database error');
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
error_log("OTP not sent. API response did not indicate success. Response: " . print_r($response, true));
|
|
||||||
jsonError('OTP not sent');
|
|
||||||
}
|
|
||||||
|
|
||||||
// دالة التعامل مع 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"]
|
|
||||||
]);
|
|
||||||
|
|
||||||
$response = curl_exec($curl);
|
|
||||||
|
|
||||||
if (curl_errno($curl)) {
|
|
||||||
error_log("cURL Error: " . curl_error($curl));
|
|
||||||
}
|
|
||||||
|
|
||||||
curl_close($curl);
|
|
||||||
|
|
||||||
return json_decode($response);
|
|
||||||
}
|
|
||||||
|
|
||||||
?>
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
<?php
|
|
||||||
// Start a session to store state and tokens.
|
|
||||||
session_start();
|
|
||||||
|
|
||||||
// 1. SETUP: Install the Google API Client Library
|
|
||||||
// Run this command in your project directory: composer require google/apiclient:^2.0
|
|
||||||
require_once __DIR__ . '/vendor/autoload.php';
|
|
||||||
|
|
||||||
// 2. CONFIGURATION: Replace with your credentials from Google Cloud Console
|
|
||||||
$clientID = '1086900987150-j8brn0i5s97315kh1ej9jr72grkfqgh5.apps.googleusercontent.com'; // Replace with your Client ID
|
|
||||||
$clientSecret = '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'; // Replace with your Client Secret
|
|
||||||
// This must be the exact URL of this script.
|
|
||||||
$redirectUri = 'https://api.tripz-egypt.com/tripz/auth/syria/auth_proxy.php'; // Replace with your script's URL
|
|
||||||
|
|
||||||
// 3. APP CONFIGURATION: Your Flutter app's custom URI scheme
|
|
||||||
// This is how the browser will redirect back to your app.
|
|
||||||
$appRedirectScheme = 'siroapp://auth'; // e.g., myapp://auth
|
|
||||||
|
|
||||||
// Create a new Google Client object
|
|
||||||
$client = new Google_Client();
|
|
||||||
$client->setClientId($clientID);
|
|
||||||
$client->setClientSecret($clientSecret);
|
|
||||||
$client->setRedirectUri($redirectUri);
|
|
||||||
$client->addScope("email");
|
|
||||||
$client->addScope("profile");
|
|
||||||
|
|
||||||
// 4. LOGIC: Handle the authentication flow
|
|
||||||
if (isset($_GET['code'])) {
|
|
||||||
// A. User has been redirected back from Google with an authorization code.
|
|
||||||
try {
|
|
||||||
// Exchange the authorization code for an access token.
|
|
||||||
$token = $client->fetchAccessTokenWithAuthCode($_GET['code']);
|
|
||||||
|
|
||||||
if (isset($token['error'])) {
|
|
||||||
// Handle error from Google
|
|
||||||
throw new Exception('Error fetching access token: ' . $token['error_description']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$client->setAccessToken($token['access_token']);
|
|
||||||
|
|
||||||
// Get user profile information from Google.
|
|
||||||
$google_oauth = new Google_Service_Oauth2($client);
|
|
||||||
$google_account_info = $google_oauth->userinfo->get();
|
|
||||||
|
|
||||||
$id = $google_account_info->id;
|
|
||||||
$email = $google_account_info->email;
|
|
||||||
$name = $google_account_info->name;
|
|
||||||
$picture = $google_account_info->picture;
|
|
||||||
|
|
||||||
// B. Redirect back to the Flutter app with the user data in the URL.
|
|
||||||
// We use urlencode to ensure data is passed correctly.
|
|
||||||
$redirectUrl = $appRedirectScheme .
|
|
||||||
'?status=success' .
|
|
||||||
'&id=' . urlencode($id) .
|
|
||||||
'&email=' . urlencode($email) .
|
|
||||||
'&name=' . urlencode($name) .
|
|
||||||
'&picture=' . urlencode($picture);
|
|
||||||
|
|
||||||
header('Location: ' . $redirectUrl);
|
|
||||||
exit();
|
|
||||||
|
|
||||||
} catch (Exception $e) {
|
|
||||||
// C. Handle any errors and redirect back to the app with an error status.
|
|
||||||
$error_message = urlencode($e->getMessage());
|
|
||||||
header('Location: ' . $appRedirectScheme . '?status=error&message=' . $error_message);
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// D. This is the initial request from the Flutter app.
|
|
||||||
// Redirect the user to Google's OAuth 2.0 server for authentication.
|
|
||||||
$authUrl = $client->createAuthUrl();
|
|
||||||
header('Location: ' . $authUrl);
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* delete_old_serial_docs.php
|
|
||||||
* يحذف صور الوثائق الأقدم من مدة محددة (افتراضي 48 ساعة) من private_uploads
|
|
||||||
* ضع الملف بجانب upload_serial_document.php ليستخدم نفس الشجرة.
|
|
||||||
*/
|
|
||||||
|
|
||||||
date_default_timezone_set('Asia/Damascus');
|
|
||||||
|
|
||||||
// === الإعدادات ===
|
|
||||||
// نفس ما في upload_serial_document.php:
|
|
||||||
const UPLOAD_ROOT = __DIR__ . "/../../private_uploads";
|
|
||||||
const ALLOWED_EXTS = ['jpg','png','webp'];
|
|
||||||
|
|
||||||
// المدة قبل الحذف (ثواني): افتراضي يومين، ويمكن تمريرها عبر CLI
|
|
||||||
$ttlSeconds = 2 * 24 * 60 * 60; // 48 ساعة
|
|
||||||
if (PHP_SAPI === 'cli' && isset($argv[1]) && ctype_digit($argv[1])) {
|
|
||||||
$ttlSeconds = (int)$argv[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
// ملف لوج اختياري
|
|
||||||
$logFile = __DIR__ . '/delete_old_serial_docs.log';
|
|
||||||
$log = @fopen($logFile, 'ab');
|
|
||||||
|
|
||||||
// دالة بسيطة للّوج
|
|
||||||
$logln = function(string $msg) use ($log) {
|
|
||||||
$line = '[' . date('Y-m-d H:i:s') . '] ' . $msg . PHP_EOL;
|
|
||||||
if ($log) @fwrite($log, $line);
|
|
||||||
};
|
|
||||||
|
|
||||||
// تحقّق أن مجلد الرفع صحيح وموجود
|
|
||||||
$root = realpath(UPLOAD_ROOT);
|
|
||||||
if ($root === false || !is_dir($root)) {
|
|
||||||
$logln("❌ UPLOAD_ROOT not found: " . UPLOAD_ROOT);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
$logln("===== Start cleanup in: {$root} | TTL={$ttlSeconds}s =====");
|
|
||||||
|
|
||||||
// مُكرّر آمن عبر RecursiveIterator
|
|
||||||
$it = new RecursiveIteratorIterator(
|
|
||||||
new RecursiveDirectoryIterator($root, FilesystemIterator::SKIP_DOTS),
|
|
||||||
RecursiveIteratorIterator::CHILD_FIRST
|
|
||||||
);
|
|
||||||
|
|
||||||
$now = time();
|
|
||||||
$deleted = 0;
|
|
||||||
$checked = 0;
|
|
||||||
|
|
||||||
// اسم الملف المتوقع: driverId__docType.ext
|
|
||||||
$docTypes = [
|
|
||||||
'driver_license_front','driver_license_back',
|
|
||||||
'car_license_front','car_license_back',
|
|
||||||
];
|
|
||||||
$docTypesRegex = implode('|', array_map('preg_quote', $docTypes));
|
|
||||||
|
|
||||||
foreach ($it as $node) {
|
|
||||||
if (!$node->isFile()) continue;
|
|
||||||
$checked++;
|
|
||||||
|
|
||||||
$path = $node->getPathname();
|
|
||||||
$ext = strtolower($node->getExtension());
|
|
||||||
|
|
||||||
// فلترة الامتدادات
|
|
||||||
if (!in_array($ext, ALLOWED_EXTS, true)) continue;
|
|
||||||
|
|
||||||
// فلترة اسم الملف (حماية من حذف ملفات أخرى)
|
|
||||||
$name = $node->getBasename();
|
|
||||||
if (!preg_match('/^[A-Za-z0-9_-]+__(' . $docTypesRegex . ')\.(jpg|png|webp)$/i', $name)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$age = $now - $node->getMTime();
|
|
||||||
if ($age >= $ttlSeconds) {
|
|
||||||
if (@unlink($path)) {
|
|
||||||
$deleted++;
|
|
||||||
$logln("🗑 Deleted: {$path} | age=" . round($age/3600, 1) . "h");
|
|
||||||
} else {
|
|
||||||
$logln("⚠️ Failed to delete: {$path}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$logln("Done. checked={$checked}, deleted={$deleted}");
|
|
||||||
if ($log) @fclose($log);
|
|
||||||
0
backend/auth/syria/driver/driver_details.php
Executable file → Normal file
0
backend/auth/syria/driver/driver_details.php
Executable file → Normal file
0
backend/auth/syria/driver/drivers_pending_list.php
Executable file → Normal file
0
backend/auth/syria/driver/drivers_pending_list.php
Executable file → Normal file
0
backend/auth/syria/driver/isPhoneVerified.php
Executable file → Normal file
0
backend/auth/syria/driver/isPhoneVerified.php
Executable file → Normal file
0
backend/auth/syria/driver/register_driver_and_car.php
Executable file → Normal file
0
backend/auth/syria/driver/register_driver_and_car.php
Executable file → Normal file
0
backend/auth/syria/driver/register_driver_and_car_signed.php
Executable file → Normal file
0
backend/auth/syria/driver/register_driver_and_car_signed.php
Executable file → Normal file
@@ -1,109 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/../../../connect.php';
|
|
||||||
//sendWhatsAppDriver.php
|
|
||||||
error_log("--- [send_otp_driver.php] Started ---");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* فحص البلاك ليست (خاصة بالسائقين)
|
|
||||||
* - يشفّر الهاتف الخام ويبحث عنه في جدول blacklist_driver
|
|
||||||
*/
|
|
||||||
function is_blacklisted_driver(PDO $con, $encryptionHelper, string $phone): bool {
|
|
||||||
$raw = trim($phone);
|
|
||||||
$enc_raw = $encryptionHelper->encryptData($raw);
|
|
||||||
|
|
||||||
$sql = "SELECT 1 FROM blacklist_driver WHERE phone = :ph LIMIT 1";
|
|
||||||
$q = $con->prepare($sql);
|
|
||||||
$q->execute(['ph' => $enc_raw]);
|
|
||||||
|
|
||||||
return (bool)$q->fetchColumn();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 0) استقبل الرقم وتحقق من البلاك ليست */
|
|
||||||
$receiver = filterRequest("receiver");
|
|
||||||
|
|
||||||
if (!$receiver) {
|
|
||||||
jsonError('Phone number is required.');
|
|
||||||
error_log("[send_otp_driver.php] Error: phone empty");
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_blacklisted_driver($con, $encryptionHelper, $receiver)) {
|
|
||||||
jsonError('This driver is blacklisted and cannot receive OTP.');
|
|
||||||
error_log("[send_otp_driver.php] BLOCKED (blacklisted): $receiver");
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 1) توليد الـ OTP (3 خانات) */
|
|
||||||
$otp = (string)rand(100, 999);
|
|
||||||
|
|
||||||
/* 2) إرسال الرمز عبر بوابة الفلاش كول / واتساب */
|
|
||||||
$nabehUrl = 'https://otp.intaleqapp.com/api/request-otp.php';
|
|
||||||
$appKey = getenv('NABEH_OTP_APP_KEY');
|
|
||||||
|
|
||||||
$phoneWithPlus = (strpos($receiver, '+') === 0) ? $receiver : '+' . $receiver;
|
|
||||||
|
|
||||||
$payload = [
|
|
||||||
'phone' => $phoneWithPlus,
|
|
||||||
'device_type' => 'android',
|
|
||||||
'method' => 'whatsapp',
|
|
||||||
'code' => $otp
|
|
||||||
];
|
|
||||||
|
|
||||||
$ch = curl_init($nabehUrl);
|
|
||||||
curl_setopt_array($ch, [
|
|
||||||
CURLOPT_POST => true,
|
|
||||||
CURLOPT_RETURNTRANSFER => true,
|
|
||||||
CURLOPT_POSTFIELDS => json_encode($payload),
|
|
||||||
CURLOPT_HTTPHEADER => [
|
|
||||||
'Content-Type: application/json',
|
|
||||||
"X-App-Key: $appKey"
|
|
||||||
],
|
|
||||||
CURLOPT_TIMEOUT => 15,
|
|
||||||
CURLOPT_CONNECTTIMEOUT => 5
|
|
||||||
]);
|
|
||||||
|
|
||||||
$res = curl_exec($ch);
|
|
||||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
||||||
$error = curl_error($ch);
|
|
||||||
curl_close($ch);
|
|
||||||
|
|
||||||
if ($error) {
|
|
||||||
error_log("⚠️ [Flash Call OTP Driver] Curl Error: $error");
|
|
||||||
jsonError('Failed to connect to OTP service');
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$decoded = json_decode((string)$res, true);
|
|
||||||
if ($httpCode !== 200 || !($decoded['success'] ?? false)) {
|
|
||||||
error_log("❌ [Flash Call OTP Driver] Failed response: Code $httpCode | Body: " . (string)$res);
|
|
||||||
jsonError($decoded['message'] ?? 'Failed to request verification code');
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 3) حفظ الـ OTP في قاعدة البيانات */
|
|
||||||
$receiver_enc = $encryptionHelper->encryptData($receiver);
|
|
||||||
$otp_enc = $encryptionHelper->encryptData($otp);
|
|
||||||
|
|
||||||
$exp = date('Y-m-d H:i:s', strtotime('+5 minutes'));
|
|
||||||
$now = date('Y-m-d H:i:s');
|
|
||||||
|
|
||||||
try {
|
|
||||||
// حذف أي رموز سابقة لنفس الرقم
|
|
||||||
$con->prepare("DELETE FROM phone_verification WHERE phone_number = ?")
|
|
||||||
->execute([$receiver_enc]);
|
|
||||||
|
|
||||||
$stmt = $con->prepare("
|
|
||||||
INSERT INTO phone_verification
|
|
||||||
(phone_number, token_code, expiration_time, is_verified, created_at)
|
|
||||||
VALUES (?, ?, ?, 0, ?)
|
|
||||||
");
|
|
||||||
$stmt->execute([$receiver_enc, $otp_enc, $exp, $now]);
|
|
||||||
|
|
||||||
jsonSuccess(null, 'OTP sent and saved successfully');
|
|
||||||
error_log("[send_otp_driver.php] OTP saved for driver $receiver");
|
|
||||||
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
error_log("[send_otp_driver.php] DB error: ".$e->getMessage());
|
|
||||||
jsonError('OTP generated but failed to save to database');
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/../../../connect.php';
|
|
||||||
|
|
||||||
$phoneNumber = filterRequest("phone_number");
|
|
||||||
$otp = filterRequest("otp");
|
|
||||||
$email = $phoneNumber . '@intaleqapp.com';
|
|
||||||
|
|
||||||
error_log("📥 [verifyOtp.php] Received phone number: $phoneNumber | OTP: $otp");
|
|
||||||
|
|
||||||
if (empty($phoneNumber) || empty($otp)) {
|
|
||||||
jsonError("Phone number and OTP are required.");
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 🔐 تشفير البيانات
|
|
||||||
$phoneNumber_encrypted = $encryptionHelper->encryptData($phoneNumber);
|
|
||||||
$email_encrypted = $encryptionHelper->encryptData($email);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 🔍 1. التحقق من السجل المخزن في قاعدة البيانات
|
|
||||||
$stmtSelect = $con->prepare("SELECT * FROM phone_verification WHERE phone_number = ? ORDER BY created_at DESC LIMIT 1");
|
|
||||||
$stmtSelect->execute([$phoneNumber_encrypted]);
|
|
||||||
$record = $stmtSelect->fetch(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
if (!$record) {
|
|
||||||
jsonError("Verification session not found. Please request a new code.");
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 🔍 2. فك تشفير ومقارنة الرمز
|
|
||||||
$decryptedOtp = $encryptionHelper->decryptData($record['token_code']);
|
|
||||||
if ($decryptedOtp !== $otp) {
|
|
||||||
jsonError("Invalid verification code.");
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 🔍 3. التحقق من الصلاحية
|
|
||||||
$now = date('Y-m-d H:i:s');
|
|
||||||
if ($record['expiration_time'] && $record['expiration_time'] < $now) {
|
|
||||||
jsonError("Verification code has expired. Please request a new one.");
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 🧹 حذف أي رموز قديمة لنفس الرقم
|
|
||||||
$con->prepare("DELETE FROM phone_verification WHERE phone_number = ?")
|
|
||||||
->execute([$phoneNumber_encrypted]);
|
|
||||||
|
|
||||||
// 🧾 توليد driverID فريد
|
|
||||||
$raw = $phoneNumber;
|
|
||||||
$driverID = substr(md5($raw), 2, 20);
|
|
||||||
|
|
||||||
// 🔐 توليد رمز تجريبي (بدون OTP حقيقي لتجنب Null)
|
|
||||||
$dummyToken = $encryptionHelper->encryptData('AUTO');
|
|
||||||
|
|
||||||
// ✅ إدخال سجل تحقق مباشر
|
|
||||||
$stmt = $con->prepare("
|
|
||||||
INSERT INTO phone_verification
|
|
||||||
(phone_number, token_code, email, driverId, expiration_time, is_verified, created_at)
|
|
||||||
VALUES (?, ?, ?, ?, NULL, 1, ?)
|
|
||||||
");
|
|
||||||
$stmt->execute([$phoneNumber_encrypted, $dummyToken, $email_encrypted, $driverID, $now]);
|
|
||||||
|
|
||||||
error_log("✅ [verifyOtp.php] Verification record inserted successfully for $phoneNumber");
|
|
||||||
|
|
||||||
// 🔍 التحقق إذا السائق موجود مسبقاً
|
|
||||||
$checkDriverStmt = $con->prepare("SELECT * FROM driver WHERE phone = ?");
|
|
||||||
$checkDriverStmt->execute([$phoneNumber_encrypted]);
|
|
||||||
$driver = $checkDriverStmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
if ($driver) {
|
|
||||||
error_log("👤 [verifyOtp.php] Driver already registered. Returning driver info.");
|
|
||||||
printSuccess([
|
|
||||||
"message" => "Driver already registered.",
|
|
||||||
"isRegistered" => true,
|
|
||||||
"driver" => [
|
|
||||||
"id" => $driver['id'],
|
|
||||||
"first_name" => $encryptionHelper->decryptData($driver['first_name']),
|
|
||||||
"last_name" => $encryptionHelper->decryptData($driver['last_name']),
|
|
||||||
"email" => $encryptionHelper->decryptData($driver['email']),
|
|
||||||
"phone" => $phoneNumber
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
error_log("🆕 [verifyOtp.php] Phone verified. Driver not found.");
|
|
||||||
printSuccess([
|
|
||||||
"message" => "Phone number verified successfully.",
|
|
||||||
"isRegistered" => false,
|
|
||||||
"driverID" => $driverID
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
error_log("💥 [verifyOtp.php] PDO ERROR: " . $e->getMessage());
|
|
||||||
jsonError("Database error: " . $e->getMessage());
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
0
backend/auth/syria/register_passenger.php
Executable file → Normal file
0
backend/auth/syria/register_passenger.php
Executable file → Normal file
@@ -1,72 +0,0 @@
|
|||||||
<?php
|
|
||||||
// File: secure_image.php
|
|
||||||
// يعرض الملف فقط إذا كان الرابط موقّع وصالح زمنياً.
|
|
||||||
// يعتمد نفس الثوابت/المسارات في upload_serial_document.php
|
|
||||||
|
|
||||||
require_once __DIR__ . '/../../connect.php';
|
|
||||||
|
|
||||||
const UPLOAD_ROOT = __DIR__ . "/../../private_uploads";
|
|
||||||
const SIGN_SECRET = getenv('SECRET_KEY_HMAC'); // نفس المفتاح
|
|
||||||
|
|
||||||
// استلام المعطيات من الرابط
|
|
||||||
$driverId = $_GET['driver_id'] ?? '';
|
|
||||||
$docType = $_GET['doc_type'] ?? '';
|
|
||||||
$extShort = $_GET['ext'] ?? '';
|
|
||||||
$expires = $_GET['expires'] ?? '';
|
|
||||||
$signature= $_GET['signature'] ?? '';
|
|
||||||
|
|
||||||
if ($driverId === '' || $docType === '' || $extShort === '' || $expires === '' || $signature === '') {
|
|
||||||
http_response_code(400); echo "Missing parameters."; exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// صلاحية الوقت
|
|
||||||
if ((int)$expires < time()) {
|
|
||||||
http_response_code(403); echo "Link expired."; exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// تحقق من doc_type
|
|
||||||
$allowedDocTypes = [
|
|
||||||
'driver_license_front',
|
|
||||||
'driver_license_back',
|
|
||||||
'car_license_front',
|
|
||||||
'car_license_back',
|
|
||||||
];
|
|
||||||
if (!in_array($docType, $allowedDocTypes, true)) {
|
|
||||||
http_response_code(403); echo "Invalid doc_type."; exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// تحقق من الامتداد
|
|
||||||
$allowedExts = ['jpg','png','webp'];
|
|
||||||
if (!in_array($extShort, $allowedExts, true)) {
|
|
||||||
http_response_code(403); echo "Invalid ext."; exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// إعادة توليد التوقيع للمقارنة
|
|
||||||
$driverIdSafe = preg_replace('/[^A-Za-z0-9_\-]/', '_', $driverId);
|
|
||||||
$message = $driverIdSafe . ':' . $docType . ':' . $extShort . ':' . $expires;
|
|
||||||
$expected = hash_hmac('sha256', $message, SIGN_SECRET);
|
|
||||||
|
|
||||||
if (!hash_equals($expected, $signature)) {
|
|
||||||
http_response_code(403); echo "Invalid signature."; exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// بناء المسار
|
|
||||||
$h = hash('sha1', $driverIdSafe);
|
|
||||||
$subdir = substr($h, 0, 2) . '/' . substr($h, 2, 2);
|
|
||||||
$serverName = "{$driverIdSafe}__{$docType}.{$extShort}";
|
|
||||||
$path = UPLOAD_ROOT . '/' . $subdir . '/' . $serverName;
|
|
||||||
|
|
||||||
if (!is_file($path)) {
|
|
||||||
http_response_code(404); echo "File not found."; exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// تحديد النوع
|
|
||||||
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
|
||||||
$mime = $finfo->file($path) ?: 'application/octet-stream';
|
|
||||||
|
|
||||||
header('Content-Type: ' . $mime);
|
|
||||||
header('Content-Length: ' . filesize($path));
|
|
||||||
header('X-Content-Type-Options: nosniff');
|
|
||||||
// (اختياري) اطلب توكن وصول إضافي عبر Authorization للتحكم الأدق.
|
|
||||||
// مثال: تحقق من $_SERVER['HTTP_AUTHORIZATION'] هنا إن أردت.
|
|
||||||
readfile($path);
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
<?php
|
|
||||||
$allowRegistration = true;
|
|
||||||
require_once __DIR__ . '/../../connect.php';
|
|
||||||
|
|
||||||
error_log("--- [send_otp_pass.php] Started ---");
|
|
||||||
|
|
||||||
/* Helpers */
|
|
||||||
function normalize_phone($s) { return preg_replace('/\D+/', '', (string)$s); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check blacklist by encrypted phone
|
|
||||||
*/
|
|
||||||
function is_blacklisted(PDO $con, $encryptionHelper, string $phone): bool {
|
|
||||||
$raw = trim($phone);
|
|
||||||
$norm = normalize_phone($raw);
|
|
||||||
|
|
||||||
$enc_raw = $encryptionHelper->encryptData($raw);
|
|
||||||
$enc_norm = $encryptionHelper->encryptData($norm);
|
|
||||||
|
|
||||||
$sql = "SELECT 1
|
|
||||||
FROM passenger_blacklist
|
|
||||||
WHERE phone IN (:enc_raw, :enc_norm)
|
|
||||||
AND (expires_at IS NULL OR expires_at > NOW())
|
|
||||||
LIMIT 1";
|
|
||||||
|
|
||||||
$q = $con->prepare($sql);
|
|
||||||
$q->execute([
|
|
||||||
'enc_raw' => $enc_raw,
|
|
||||||
'enc_norm' => $enc_norm,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (bool)$q->fetchColumn();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 0) Get phone number */
|
|
||||||
$receiver = filterRequest("receiver");
|
|
||||||
if (!$receiver) {
|
|
||||||
jsonError('Phone number is required.');
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_blacklisted($con, $encryptionHelper, $receiver)) {
|
|
||||||
jsonError('This phone is blacklisted and cannot receive OTP.');
|
|
||||||
error_log("[send_otp] BLOCKED (blacklisted): $receiver");
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 1) Generate OTP (3 digits) */
|
|
||||||
$otp = (string)rand(100, 999);
|
|
||||||
|
|
||||||
/* 2) Send via Flash Call / WhatsApp Gateway */
|
|
||||||
$nabehUrl = 'https://otp.intaleqapp.com/api/request-otp.php';
|
|
||||||
$appKey = getenv('NABEH_OTP_APP_KEY');
|
|
||||||
|
|
||||||
$phoneWithPlus = (strpos($receiver, '+') === 0) ? $receiver : '+' . $receiver;
|
|
||||||
|
|
||||||
$payload = [
|
|
||||||
'phone' => $phoneWithPlus,
|
|
||||||
'device_type' => 'android',
|
|
||||||
'method' => 'whatsapp',
|
|
||||||
'code' => $otp
|
|
||||||
];
|
|
||||||
|
|
||||||
$ch = curl_init($nabehUrl);
|
|
||||||
curl_setopt_array($ch, [
|
|
||||||
CURLOPT_POST => true,
|
|
||||||
CURLOPT_RETURNTRANSFER => true,
|
|
||||||
CURLOPT_POSTFIELDS => json_encode($payload),
|
|
||||||
CURLOPT_HTTPHEADER => [
|
|
||||||
'Content-Type: application/json',
|
|
||||||
"X-App-Key: $appKey"
|
|
||||||
],
|
|
||||||
CURLOPT_TIMEOUT => 15,
|
|
||||||
CURLOPT_CONNECTTIMEOUT => 5
|
|
||||||
]);
|
|
||||||
|
|
||||||
$res = curl_exec($ch);
|
|
||||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
||||||
$error = curl_error($ch);
|
|
||||||
curl_close($ch);
|
|
||||||
|
|
||||||
if ($error) {
|
|
||||||
error_log("⚠️ [Flash Call OTP Passenger] Curl Error: $error");
|
|
||||||
jsonError('Failed to connect to OTP service');
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$decoded = json_decode((string)$res, true);
|
|
||||||
if ($httpCode !== 200 || !($decoded['success'] ?? false)) {
|
|
||||||
error_log("❌ [Flash Call OTP Passenger] Failed response: Code $httpCode | Body: " . (string)$res);
|
|
||||||
jsonError($decoded['message'] ?? 'Failed to request verification code');
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 3) Save OTP (encrypted) */
|
|
||||||
$receiver_enc = $encryptionHelper->encryptData($receiver);
|
|
||||||
$otp_enc = $encryptionHelper->encryptData($otp);
|
|
||||||
|
|
||||||
$exp = date('Y-m-d H:i:s', strtotime('+5 minutes'));
|
|
||||||
$now = date('Y-m-d H:i:s');
|
|
||||||
|
|
||||||
try {
|
|
||||||
$con->prepare("DELETE FROM phone_verification_passenger WHERE phone_number = ?")
|
|
||||||
->execute([$receiver_enc]);
|
|
||||||
|
|
||||||
$stmt = $con->prepare("
|
|
||||||
INSERT INTO phone_verification_passenger
|
|
||||||
(phone_number, token, expiration_time, verified, created_at)
|
|
||||||
VALUES (?, ?, ?, 0, ?)
|
|
||||||
");
|
|
||||||
$stmt->execute([$receiver_enc, $otp_enc, $exp, $now]);
|
|
||||||
|
|
||||||
jsonSuccess(null, 'OTP sent and saved successfully');
|
|
||||||
error_log("[send_otp] OTP saved successfully for $receiver");
|
|
||||||
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
error_log("[send_otp] DB error: ".$e->getMessage());
|
|
||||||
jsonError('OTP generated but failed to save to database');
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/../../connect.php';
|
|
||||||
|
|
||||||
$receiver = filterRequest("receiver");
|
|
||||||
if (!$receiver) {
|
|
||||||
jsonError("رقم الهاتف مفقود");
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// توليد تأخير عشوائي بين 45 و 90 ثانية
|
|
||||||
$delay = rand(45, 90);
|
|
||||||
sleep($delay);
|
|
||||||
|
|
||||||
// رسالة الاستطلاع مع أزرار
|
|
||||||
$surveyMessage = [
|
|
||||||
"type" => "buttons",
|
|
||||||
"header" => [
|
|
||||||
"type" => "text",
|
|
||||||
"text" => "استطلاع رأي سريع 🌟"
|
|
||||||
],
|
|
||||||
"body" => [
|
|
||||||
"text" => "هل كانت تجربة التسجيل في تطبيق *سيرو* سهلة بالنسبة لك؟\n\n👇 اضغط أحد الخيارات:"
|
|
||||||
],
|
|
||||||
"footer" => [
|
|
||||||
"text" => "للتواصل: +962 7XXXXXXX - رابط التطبيق: https://intaleq.xyz"
|
|
||||||
],
|
|
||||||
"buttons" => [
|
|
||||||
[
|
|
||||||
"type" => "reply",
|
|
||||||
"reply" => [
|
|
||||||
"id" => "feedback_yes",
|
|
||||||
"title" => "👍 نعم"
|
|
||||||
]
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"type" => "reply",
|
|
||||||
"reply" => [
|
|
||||||
"id" => "feedback_no",
|
|
||||||
"title" => "👎 لا"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
];
|
|
||||||
|
|
||||||
// استدعاء الدالة لإرسال الرسالة
|
|
||||||
$response = sendWhatsAppFromServer($receiver, $surveyMessage);
|
|
||||||
if ($response && isset($response["status"]) && $response["status"] === "sent") {
|
|
||||||
jsonSuccess(null, "تم إرسال استطلاع الرأي بنجاح بعد $delay ثانية.");
|
|
||||||
} else {
|
|
||||||
jsonError("فشل في إرسال استطلاع الرأي");
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
0
backend/auth/syria/uploadSyrianDocs.php
Executable file → Normal file
0
backend/auth/syria/uploadSyrianDocs.php
Executable file → Normal file
@@ -1,114 +0,0 @@
|
|||||||
<?php
|
|
||||||
$allowRegistration = true;
|
|
||||||
require_once __DIR__ . '/../../connect.php';
|
|
||||||
|
|
||||||
// تسجيل بداية الطلب
|
|
||||||
error_log("[Auth_Debug] Start processing phone verification request.");
|
|
||||||
|
|
||||||
$phoneNumber = filterRequest("phone_number");
|
|
||||||
$otp = filterRequest("otp");
|
|
||||||
|
|
||||||
if (!$phoneNumber) {
|
|
||||||
error_log("[Auth_Error] Phone number is missing in the request.");
|
|
||||||
jsonError("Phone number is required");
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$otp) {
|
|
||||||
error_log("[Auth_Error] OTP is missing in the request.");
|
|
||||||
jsonError("OTP is required");
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
// تسجيل الرقم
|
|
||||||
error_log("[Auth_Debug] Received phone number (Masked): " . substr($phoneNumber, 0, 7) . "***** | OTP: " . $otp);
|
|
||||||
|
|
||||||
// تشفير رقم الهاتف
|
|
||||||
$phoneNumber_encrypted = $encryptionHelper->encryptData($phoneNumber);
|
|
||||||
error_log("[Auth_Debug] Phone number encrypted successfully.");
|
|
||||||
|
|
||||||
try {
|
|
||||||
// ✅ 1. التحقق من السجل المخزن في قاعدة البيانات
|
|
||||||
$stmtSelect = $con->prepare("SELECT * FROM phone_verification_passenger WHERE phone_number = ? ORDER BY created_at DESC LIMIT 1");
|
|
||||||
$stmtSelect->execute([$phoneNumber_encrypted]);
|
|
||||||
$record = $stmtSelect->fetch(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
if (!$record) {
|
|
||||||
error_log("[Auth_Error] No verification record found for this number.");
|
|
||||||
jsonError("Verification session not found. Please request a new code.");
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ 2. فك تشفير ومقارنة الرمز
|
|
||||||
$decryptedOtp = $encryptionHelper->decryptData($record['token']);
|
|
||||||
if ($decryptedOtp !== $otp) {
|
|
||||||
error_log("[Auth_Error] OTP mismatch. Expected: $decryptedOtp, Got: $otp");
|
|
||||||
jsonError("Invalid verification code.");
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ 3. التحقق من الصلاحية (خلال 5 دقائق)
|
|
||||||
$now = date('Y-m-d H:i:s');
|
|
||||||
if ($record['expiration_time'] && $record['expiration_time'] < $now) {
|
|
||||||
error_log("[Auth_Error] OTP expired.");
|
|
||||||
jsonError("Verification code has expired. Please request a new one.");
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ 4. حذف السجلات القديمة وإدخال سجل مؤكد (verified = 1)
|
|
||||||
error_log("[Auth_Step_1] Deleting old verification records for this phone...");
|
|
||||||
$stmtDelete = $con->prepare("DELETE FROM phone_verification_passenger WHERE phone_number = ?");
|
|
||||||
$stmtDelete->execute([$phoneNumber_encrypted]);
|
|
||||||
|
|
||||||
$stmtInsert = $con->prepare("
|
|
||||||
INSERT INTO phone_verification_passenger (phone_number, token, expiration_time, verified, created_at)
|
|
||||||
VALUES (?, NULL, NULL, 1, ?)
|
|
||||||
");
|
|
||||||
$stmtInsert->execute([$phoneNumber_encrypted, $now]);
|
|
||||||
error_log("[Auth_Step_1] Inserted verified record.");
|
|
||||||
|
|
||||||
// ✅ 5. فحص هل الراكب موجود مسبقاً
|
|
||||||
error_log("[Auth_Step_3] Checking if passenger exists in passengers table...");
|
|
||||||
|
|
||||||
$checkPassengerStmt = $con->prepare("
|
|
||||||
SELECT * FROM passengers WHERE phone = ?
|
|
||||||
");
|
|
||||||
$checkPassengerStmt->execute([$phoneNumber_encrypted]);
|
|
||||||
$passenger = $checkPassengerStmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
if ($passenger) {
|
|
||||||
// ✅ الراكب موجود
|
|
||||||
error_log("[Auth_Result] Passenger Found. ID: " . $passenger['id']);
|
|
||||||
|
|
||||||
printSuccess([
|
|
||||||
"message" => "Passenger already registered.",
|
|
||||||
"isRegistered" => true,
|
|
||||||
"passenger" => [
|
|
||||||
"id" => $passenger['id'],
|
|
||||||
"first_name" => $encryptionHelper->decryptData($passenger['first_name']),
|
|
||||||
"last_name" => $encryptionHelper->decryptData($passenger['last_name']),
|
|
||||||
"email" => $encryptionHelper->decryptData($passenger['email']),
|
|
||||||
"phone" => $phoneNumber
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
// ✅ الراكب جديد
|
|
||||||
error_log("[Auth_Result] Passenger Not Found. Treating as new user.");
|
|
||||||
|
|
||||||
printSuccess([
|
|
||||||
"message" => "Phone number verified successfully.",
|
|
||||||
"isRegistered" => false
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
error_log("[Auth_DB_Exception] Error: " . $e->getMessage() . " | File: " . $e->getFile() . " | Line: " . $e->getLine());
|
|
||||||
jsonError("Database error occurred. Please contact support.");
|
|
||||||
} catch (Exception $e) {
|
|
||||||
error_log("[Auth_General_Exception] Error: " . $e->getMessage());
|
|
||||||
jsonError("An unexpected error occurred.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// تسجيل نهاية الطلب
|
|
||||||
error_log("[Auth_Debug] Request processing finished.");
|
|
||||||
?>
|
|
||||||
0
backend/auth/token_passenger/driver/send_otp_driver.php
Executable file → Normal file
0
backend/auth/token_passenger/driver/send_otp_driver.php
Executable file → Normal file
0
backend/auth/token_passenger/driver/verify_otp_driver.php
Executable file → Normal file
0
backend/auth/token_passenger/driver/verify_otp_driver.php
Executable file → Normal file
0
backend/auth/token_passenger/send_otp.php
Executable file → Normal file
0
backend/auth/token_passenger/send_otp.php
Executable file → Normal file
0
backend/auth/token_passenger/verify_otp.php
Executable file → Normal file
0
backend/auth/token_passenger/verify_otp.php
Executable file → Normal file
@@ -15,7 +15,7 @@ if ($result) {
|
|||||||
$stmt = $con->prepare($sql);
|
$stmt = $con->prepare($sql);
|
||||||
$stmt->execute([':id' => $id]);
|
$stmt->execute([':id' => $id]);
|
||||||
|
|
||||||
$admin='support@sefer.com';
|
$admin='support@siromove.com';
|
||||||
$headers = "MIME-Version: 1.0" . "\r\n";
|
$headers = "MIME-Version: 1.0" . "\r\n";
|
||||||
$headers .= "Content-type: text/html; charset=UTF-8" . "\r\n";
|
$headers .= "Content-type: text/html; charset=UTF-8" . "\r\n";
|
||||||
$headers .= "From: $admin" . "\r\n";
|
$headers .= "From: $admin" . "\r\n";
|
||||||
@@ -28,7 +28,7 @@ Hi [$email],
|
|||||||
Your email address has been verified.
|
Your email address has been verified.
|
||||||
|
|
||||||
Thank you,
|
Thank you,
|
||||||
SEFER Team";
|
Siro Team";
|
||||||
|
|
||||||
mail($email, $subject, $bodyEmail, $headers);
|
mail($email, $subject, $bodyEmail, $headers);
|
||||||
|
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/../connect.php';
|
|
||||||
|
|
||||||
$phone_number = filterRequest("phone_number");
|
|
||||||
$token_code = filterRequest("token");
|
|
||||||
|
|
||||||
// Check if the phone number and token code match
|
|
||||||
$sql = "SELECT
|
|
||||||
`id`,
|
|
||||||
`phone_number`,
|
|
||||||
`token`,
|
|
||||||
`expiration_time`,
|
|
||||||
`verified`,
|
|
||||||
`created_at`
|
|
||||||
FROM
|
|
||||||
`phone_verification_passenger`
|
|
||||||
WHERE
|
|
||||||
`phone_number` = :phone_number AND `token` = :token_code";
|
|
||||||
|
|
||||||
$stmt = $con->prepare($sql);
|
|
||||||
|
|
||||||
// Log the parameters used in the SQL query for debugging
|
|
||||||
error_log("Executing SELECT SQL: " . $sql . " with phone_number=" . $phone_number . " and token_code=" . $token_code);
|
|
||||||
|
|
||||||
$stmt->bindParam(':phone_number', $phone_number, PDO::PARAM_STR);
|
|
||||||
$stmt->bindParam(':token_code', $token_code, PDO::PARAM_STR);
|
|
||||||
|
|
||||||
if ($stmt->execute()) {
|
|
||||||
$result = $stmt->fetch();
|
|
||||||
|
|
||||||
if ($result) {
|
|
||||||
// Update the verified status
|
|
||||||
$sql = "UPDATE `phone_verification_passenger` SET `verified` = 1 WHERE `phone_number` = :phone_number";
|
|
||||||
$stmt = $con->prepare($sql);
|
|
||||||
|
|
||||||
// Log the update query execution
|
|
||||||
error_log("Executing UPDATE SQL: " . $sql . " with phone_number=" . $phone_number);
|
|
||||||
|
|
||||||
$stmt->bindParam(':phone_number', $phone_number, PDO::PARAM_STR);
|
|
||||||
|
|
||||||
if ($stmt->execute()) {
|
|
||||||
jsonSuccess(null, "Your phone number has been verified.");
|
|
||||||
} else {
|
|
||||||
// Log if the update query fails
|
|
||||||
error_log("Error executing UPDATE SQL: " . implode(", ", $stmt->errorInfo()));
|
|
||||||
jsonError("An error occurred while verifying your phone number. Please try again.");
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Log if no matching record was found
|
|
||||||
error_log("No matching record found for phone_number=" . $phone_number . " and token_code=" . $token_code);
|
|
||||||
jsonError("Your phone number could not be verified. Please try again.");
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Log if the select query fails
|
|
||||||
error_log("Error executing SELECT SQL: " . implode(", ", $stmt->errorInfo()));
|
|
||||||
jsonError("An error occurred while verifying your phone number. Please try again.");
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
0
backend/composer.json
Executable file → Normal file
0
backend/composer.json
Executable file → Normal file
0
backend/composer.lock
generated
Executable file → Normal file
0
backend/composer.lock
generated
Executable file → Normal file
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user