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');
|
||||
|
||||
// التحقق من الصلاحيات: فقط المشرفين يمكنهم الإضافة
|
||||
// إذا لم يكن هناك أي مدير في النظام، نسمح// تم تعطيل التحقق للسماح بإعادة التهيئة في مرحلة التطوير
|
||||
// $count = $con->query("SELECT COUNT(*) FROM adminUser")->fetchColumn();
|
||||
// if ($count > 0) die("Access Denied: Admin already initialized.");
|
||||
// $auth = JwtService::authenticate($redis);
|
||||
// if ($auth['role'] !== 'super_admin' && $auth['role'] !== 'admin') {
|
||||
// jsonError("Unauthorized. Only Admins can add staff.");
|
||||
// exit;
|
||||
// }
|
||||
// التحقق من الصلاحيات: فقط المشرفين (super_admin أو admin) يمكنهم الإضافة
|
||||
$jwtService = new JwtService($redis);
|
||||
$auth = $jwtService->authenticate();
|
||||
$authRole = $auth->role ?? '';
|
||||
if ($authRole !== 'super_admin' && $authRole !== 'admin') {
|
||||
jsonError("غير مصرح لك. فقط المشرفون يمكنهم إضافة موظفين.");
|
||||
exit;
|
||||
}
|
||||
|
||||
$name = filterRequest("name");
|
||||
$phone = filterRequest("phone");
|
||||
@@ -47,14 +46,20 @@ try {
|
||||
|
||||
if ($role === 'admin') {
|
||||
// الإضافة لجدول المديرين
|
||||
$sql = "INSERT INTO adminUser (id, fingerprint, fingerprint_hash, name, password, role, created_at)
|
||||
VALUES (:id, :fp, :fp_hash, :name, :pass, :role, NOW())";
|
||||
// التأكد من وجود عمود phone في الجدول (كإجراء احترازي لتجنب الأخطاء إذا لم يكن موجوداً)
|
||||
try {
|
||||
$con->exec("ALTER TABLE adminUser ADD COLUMN phone VARCHAR(255) NULL AFTER name");
|
||||
} catch (Exception $e) { /* العمود موجود مسبقاً */ }
|
||||
|
||||
$sql = "INSERT INTO adminUser (id, fingerprint, fingerprint_hash, name, phone, password, role, created_at)
|
||||
VALUES (:id, :fp, :fp_hash, :name, :phone, :pass, :role, NOW())";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute([
|
||||
':id' => $uniqueId,
|
||||
':fp' => $encFp,
|
||||
':fp_hash' => $fpHash,
|
||||
':name' => $encName,
|
||||
':phone' => $encPhone,
|
||||
':pass' => $hashedPassword,
|
||||
':role' => $role
|
||||
]);
|
||||
|
||||
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');
|
||||
$password = filterRequest('password');
|
||||
$phone = filterRequest('phone');
|
||||
$audience = filterRequest('aud') ?? 'admin';
|
||||
$isRenewal = filterRequest('is_renewal') === '1';
|
||||
|
||||
@@ -16,6 +17,10 @@ if (empty($fingerprint) || empty($password)) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Rate Limiting: 5 محاولات في الدقيقة لكل IP
|
||||
$rateLimiter = new RateLimiter($redis);
|
||||
$rateLimiter->enforce(RateLimiter::identifier(), 'login');
|
||||
|
||||
try {
|
||||
$con = Database::get('main');
|
||||
|
||||
@@ -25,6 +30,28 @@ try {
|
||||
$stmt->execute([':fp' => $fpHash]);
|
||||
$admin = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// إذا لم يتم العثور بالبصمة، وتم تمرير رقم الهاتف (تسجيل دخول لأول مرة أو جهاز جديد)
|
||||
if (!$admin && !empty($phone)) {
|
||||
$encPhoneInput = $encryptionHelper->encryptData($phone);
|
||||
$stmtPhone = $con->prepare("SELECT * FROM adminUser WHERE phone = :phone LIMIT 1");
|
||||
$stmtPhone->execute([':phone' => $encPhoneInput]);
|
||||
$admin = $stmtPhone->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// تأكيد كلمة المرور وتحديث بصمة الجهاز إذا تم إيجاد الحساب
|
||||
if ($admin && password_verify($password, $admin['password'])) {
|
||||
$encFpRaw = $encryptionHelper->encryptData($fingerprint);
|
||||
$updateStmt = $con->prepare("UPDATE adminUser SET fingerprint = :fp_raw, fingerprint_hash = :fp WHERE id = :id");
|
||||
$updateStmt->execute([
|
||||
':fp_raw' => $encFpRaw,
|
||||
':fp' => $fpHash,
|
||||
':id' => $admin['id']
|
||||
]);
|
||||
$admin['fingerprint_hash'] = $fpHash; // Update locally
|
||||
} else if ($admin) {
|
||||
// Password incorrect, fail later.
|
||||
}
|
||||
}
|
||||
|
||||
if ($admin) {
|
||||
// 1. التحقق من حالة الحساب
|
||||
if ($admin['status'] === 'pending') {
|
||||
@@ -69,8 +96,8 @@ try {
|
||||
exit;
|
||||
}
|
||||
|
||||
// 3. توليد رمز تحقق OTP وإرساله عبر WhatsApp
|
||||
$otp = rand(10000, 99999);
|
||||
// 3. توليد رمز تحقق OTP (6 أرقام) وإرساله عبر WhatsApp
|
||||
$otp = rand(100000, 999999);
|
||||
$encryptedPhone = $admin['phone'] ?? '';
|
||||
|
||||
if (empty($encryptedPhone)) {
|
||||
@@ -112,7 +139,7 @@ try {
|
||||
jsonError("كلمة المرور غير صحيحة.");
|
||||
}
|
||||
} else {
|
||||
jsonError("الجهاز غير مسجل كمشرف.");
|
||||
jsonError("الحساب أو الجهاز غير مسجل. يرجى إدخال رقم هاتفك وكلمة المرور إذا كان هذا أول تسجيل دخول لك.");
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("[Admin Login Error] " . $e->getMessage());
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<?php
|
||||
/**
|
||||
* Admin/auth/register.php
|
||||
* التسجيل الذاتي للمشرفين - الحساب يكون بحالة pending بانتظار موافقة السوبر أدمن
|
||||
* التسجيل الذاتي للمشرفين (Admins) مع التحقق من الصلاحيات من ملف .env
|
||||
*/
|
||||
require_once __DIR__ . '/../../core/bootstrap.php';
|
||||
require_once __DIR__ . '/../../functions.php';
|
||||
|
||||
$name = filterRequest('name');
|
||||
$phone = filterRequest('phone');
|
||||
@@ -11,49 +12,75 @@ $password = filterRequest('password');
|
||||
$fingerprint = filterRequest('fingerprint');
|
||||
|
||||
if (empty($name) || empty($phone) || empty($password) || empty($fingerprint)) {
|
||||
jsonError("All fields are required.");
|
||||
jsonError("جميع الحقول مطلوبة بما فيها بصمة الجهاز.");
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$con = Database::get('main');
|
||||
|
||||
// 1. التحقق من عدم وجود الحساب مسبقاً (عن طريق الهاتف أو البصمة)
|
||||
$fpHash = hash('sha256', $fingerprint);
|
||||
$check = $con->prepare("SELECT id FROM adminUser WHERE phone = ? OR fingerprint_hash = ? LIMIT 1");
|
||||
$check->execute([$phone, $fpHash]);
|
||||
|
||||
if ($check->rowCount() > 0) {
|
||||
jsonError("هذا الحساب أو الجهاز مسجل مسبقاً.");
|
||||
// 1. التحقق من البيئة (Environment Whitelist)
|
||||
$allowedPhonesStr = getenv('AUTHORIZED_ADMIN_PHONES');
|
||||
if (!$allowedPhonesStr) {
|
||||
// في حال لم يتم إعداد المتغير، نرفض الجميع للأمان
|
||||
jsonError("غير مصرح لك بالتسجيل كمشرف (القائمة البيضاء غير معدة).");
|
||||
exit;
|
||||
}
|
||||
|
||||
// 2. تجهيز البيانات
|
||||
$id = bin2hex(random_bytes(16));
|
||||
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
|
||||
$encName = $encryptionHelper->encryptData($name);
|
||||
$encFp = $encryptionHelper->encryptData($fingerprint);
|
||||
$allowedPhones = array_map('trim', explode(',', $allowedPhonesStr));
|
||||
if (!in_array($phone, $allowedPhones)) {
|
||||
jsonError("أنت غير مصرح لك بالتسجيل كمشرف. يرجى مراجعة الإدارة.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// 3. الإدخال في قاعدة البيانات (الحالة الافتراضية هي pending)
|
||||
$sql = "INSERT INTO adminUser (id, name, phone, password, fingerprint, fingerprint_hash, role, status, created_at)
|
||||
VALUES (:id, :name, :phone, :pass, :fp, :fp_hash, 'admin', 'pending', NOW())";
|
||||
$con = Database::get('main');
|
||||
|
||||
// 2. التحقق من عدم وجود الحساب مسبقاً (عن طريق الهاتف أو البصمة)
|
||||
$fpHash = hash('sha256', $fingerprint);
|
||||
$encPhoneInput = $encryptionHelper->encryptData($phone);
|
||||
|
||||
$check = $con->prepare("SELECT id FROM adminUser WHERE phone = ? OR fingerprint_hash = ? LIMIT 1");
|
||||
$check->execute([$encPhoneInput, $fpHash]);
|
||||
|
||||
if ($check->rowCount() > 0) {
|
||||
jsonError("رقم الهاتف أو الجهاز مسجل مسبقاً.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// 3. تجهيز البيانات
|
||||
$uniqueId = bin2hex(random_bytes(16)); // UUID آمن (32 حرف hex عشوائي)
|
||||
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
|
||||
|
||||
$encName = $encryptionHelper->encryptData($name);
|
||||
$encPhone = $encPhoneInput;
|
||||
$encFp = $encryptionHelper->encryptData($fingerprint);
|
||||
|
||||
// التأكد من وجود عمود phone و status في الجدول
|
||||
try {
|
||||
$con->exec("ALTER TABLE adminUser ADD COLUMN phone VARCHAR(255) NULL AFTER name");
|
||||
$con->exec("ALTER TABLE adminUser ADD COLUMN status VARCHAR(50) DEFAULT 'pending' AFTER role");
|
||||
} catch (Exception $e) { /* الأعمدة موجودة مسبقاً */ }
|
||||
|
||||
// 4. الإدخال في قاعدة البيانات بحالة pending
|
||||
$sql = "INSERT INTO adminUser (id, fingerprint, fingerprint_hash, name, phone, password, role, status, created_at)
|
||||
VALUES (:id, :fp, :fp_hash, :name, :phone, :pass, 'admin', 'pending', NOW())";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute([
|
||||
':id' => $id,
|
||||
':name' => $encName,
|
||||
':phone' => $phone,
|
||||
':pass' => $hashedPassword,
|
||||
':id' => $uniqueId,
|
||||
':fp' => $encFp,
|
||||
':fp_hash' => $fpHash
|
||||
':fp_hash' => $fpHash,
|
||||
':name' => $encName,
|
||||
':phone' => $encPhone,
|
||||
':pass' => $hashedPassword
|
||||
]);
|
||||
|
||||
printSuccess([
|
||||
"status" => "pending",
|
||||
"message" => "تم تقديم طلب التسجيل بنجاح. يرجى انتظار موافقة الإدارة."
|
||||
"message" => "تم تسجيل حسابك بنجاح وهو الآن قيد المراجعة. يرجى انتظار تفعيل المشرف العام."
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("[Admin Register Error] " . $e->getMessage());
|
||||
jsonError("خطأ في السيرفر: " . $e->getMessage());
|
||||
}
|
||||
|
||||
exit();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Rate Limiting: 3 محاولات OTP في 5 دقائق لكل IP
|
||||
$rateLimiter = new RateLimiter($redis);
|
||||
$rateLimiter->enforce(RateLimiter::identifier(), 'otp');
|
||||
|
||||
try {
|
||||
$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);
|
||||
|
||||
// دعم password_verify مع البقاء على التوافق مع كلمات السر القديمة (Plain Text)
|
||||
if ($user && (password_verify($password, $user['password']) || $user['password'] === $password)) {
|
||||
// التحقق من كلمة المرور باستخدام password_hash فقط (الأمان)
|
||||
if ($user && password_verify($password, $user['password'])) {
|
||||
|
||||
$limiter->reset(RateLimiter::identifier(), 'login');
|
||||
|
||||
|
||||
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");
|
||||
$token = filterRequest("token");
|
||||
|
||||
$admin='support@sefer.live';
|
||||
$admin='support@siromove.com';
|
||||
$headers = "MIME-Version: 1.0" . "\r\n";
|
||||
$headers .= "Content-type: text/html; charset=UTF-8" . "\r\n";
|
||||
$headers .= "From: $admin" . "\r\n";
|
||||
@@ -18,7 +18,7 @@ $bodyEmail = "
|
||||
<body>
|
||||
<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>
|
||||
$token
|
||||
@@ -26,7 +26,7 @@ $token
|
||||
<p>If you did not request to verify your email address, please ignore this email.</p>
|
||||
|
||||
<p>Thank you,</p>
|
||||
SEFER Team.
|
||||
Siro Team.
|
||||
</body>
|
||||
</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->execute([':id' => $id]);
|
||||
|
||||
$admin='support@sefer.com';
|
||||
$admin='support@siromove.com';
|
||||
$headers = "MIME-Version: 1.0" . "\r\n";
|
||||
$headers .= "Content-type: text/html; charset=UTF-8" . "\r\n";
|
||||
$headers .= "From: $admin" . "\r\n";
|
||||
@@ -28,7 +28,7 @@ Hi [$email],
|
||||
Your email address has been verified.
|
||||
|
||||
Thank you,
|
||||
SEFER Team";
|
||||
Siro Team";
|
||||
|
||||
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