Food delivery driver module + masked calls + TURN

This commit is contained in:
Hamza-Ayed
2026-08-04 01:50:50 +03:00
parent 04c214bdf6
commit f01e408ba6
35 changed files with 3029 additions and 212 deletions
+108
View File
@@ -0,0 +1,108 @@
<?php
// ═══════════════════════════════════════════════════════════════
// ride/call/turn_credentials.php
// الغرض : بيانات اعتماد مؤقتة لخادم TURN (coturn use-auth-secret)
// التطبيق: السائق والراكب معاً — نفس القناة تخدم مكالمات الرحلات
// ومكالمات توصيل الطعام المقنّعة.
// المصادقة: JWT (driver أو passenger)
// ───────────────────────────────────────────────────────────────
// لماذا بيانات مؤقتة ولا مستخدم ثابت: كلمة مرور TURN ثابتة داخل تطبيق
// منشور = مفتاح تمرير مجاني للجميع بمجرد فكّ حزمة APK. آلية coturn
// القياسية: username = "{انتهاء الصلاحية}:{المستخدم}"، والكلمة
// = base64(HMAC-SHA1(secret, username)). الخادم يتحقق حسابياً بلا
// قاعدة بيانات ولا مزامنة، والصلاحية تنتهي تلقائياً.
// ═══════════════════════════════════════════════════════════════
declare(strict_types=1);
require_once __DIR__ . '/../../core/bootstrap.php';
require_once __DIR__ . '/../../functions.php';
$limiter = new RateLimiter($redis);
$limiter->enforce(RateLimiter::identifier(), 'api');
$jwtService = new JwtService($redis);
$decoded = $jwtService->authenticate();
$role = (string)($decoded->role ?? '');
$userId = (string)($decoded->user_id ?? '');
if (!in_array($role, ['driver', 'passenger'], true) || $userId === '') {
jsonError('Forbidden — driver or passenger token required', 403);
}
// السر نفسه المُمرَّر إلى coturn — من /keys أولاً كبقية أسرار المشروع
$secret = '';
$secretPath = getenv('TURN_SECRET_PATH') ?: '/keys/turn_secret';
if (is_readable($secretPath)) {
$secret = trim((string)file_get_contents($secretPath));
}
if ($secret === '') {
$secret = (string)(getenv('TURN_STATIC_AUTH_SECRET') ?: '');
}
$turnHost = getenv('TURN_HOST') ?: '';
// بلا إعداد TURN لا نُفشل المكالمة: نُعيد STUN فقط ويبقى السلوك كما كان
// قبل إضافة TURN (اتصال مباشر ينجح في أغلب الشبكات المنزلية والـ WiFi).
if ($secret === '' || $turnHost === '') {
appLog('[TURN] not configured (missing secret or TURN_HOST) — returning STUN only', 'WARNING');
jsonSuccess([
'ice_servers' => turnDefaultStunServers(),
'ttl' => 0,
'turn_enabled' => false,
]);
}
$ttl = (int)(getenv('TURN_CREDENTIAL_TTL') ?: 43200); // 12 ساعة
$expiry = time() + $ttl;
$username = $expiry . ':' . $role . '_' . $userId;
$password = base64_encode(hash_hmac('sha1', $username, $secret, true));
$turnPort = (int)(getenv('TURN_PORT') ?: 3478);
$turnTlsPort = (int)(getenv('TURN_TLS_PORT') ?: 5349);
// الترتيب مقصود: STUN أولاً (اتصال مباشر أرخص وأقل تأخيراً)، ثم UDP TURN،
// ثم TCP/TLS على 5349 للشبكات التي تحجب UDP كلياً (بعض شبكات الشركات).
$iceServers = turnDefaultStunServers();
$iceServers[] = [
'urls' => "turn:{$turnHost}:{$turnPort}?transport=udp",
'username' => $username,
'credential' => $password,
];
$iceServers[] = [
'urls' => "turn:{$turnHost}:{$turnPort}?transport=tcp",
'username' => $username,
'credential' => $password,
];
// turns: يُعلَن فقط إذا كانت شهادة TLS مركّبة فعلاً على coturn — وإلا صار
// مساراً ميتاً في قائمة ICE يُبطئ التفاوض بلا أن يعمل أبداً.
if (getenv('TURN_TLS_ENABLED') === 'true') {
$iceServers[] = [
'urls' => "turns:{$turnHost}:{$turnTlsPort}?transport=tcp",
'username' => $username,
'credential' => $password,
];
}
jsonSuccess([
'ice_servers' => $iceServers,
'ttl' => $ttl,
'expires_at' => $expiry,
'turn_enabled' => true,
]);
function turnDefaultStunServers(): array
{
$host = getenv('TURN_HOST') ?: '';
$port = (int)(getenv('TURN_PORT') ?: 3478);
// خادمنا يعمل STUN أيضاً — نضعه أولاً، ونُبقي خوادم Google احتياطاً
// إن سقط خادمنا (اكتشاف العنوان العام فقط، لا يمرّ عبره أي صوت).
$servers = [];
if ($host !== '') {
$servers[] = ['urls' => "stun:{$host}:{$port}"];
}
$servers[] = ['urls' => 'stun:stun.l.google.com:19302'];
$servers[] = ['urls' => 'stun:stun1.l.google.com:19302'];
return $servers;
}