feat: استيراد كود سيرو إلى تريبز (سيرو @ecfe7568) — بلا تعديل
قرار المالك 2026-07-27: باك إند سيرو PHP هو المعتمد، وتطبيقاته المجرّبة ميدانياً تحل محل إعادة البناء المؤرشفة. سيرو نفسه لم يُمسّ. الخريطة: backend · payment_server · loction_server · ride_server · passenger_server · docker · dashboard · stress_test → الجذر siro_rider → apps/rider siro_driver → apps/driver siro_admin → dashboards/admin siro_service → dashboards/service android_bot → apps/android_bot socialBot → apps/socialBot نُسخ المتعقَّب في git سيرو فقط عبر `git archive` (3,198 ملفاً / ~169 م.ب) لا `cp -r` — فاستُثنيت مخلفات البناء تلقائياً. بلا أي تعديل محتوى عمداً: كل ما يلي يصير فرقاً مقروءاً مقابل المصدر. لم يُستورد وسببه: siromove.com (الموقع التسويقي يبقى marketing/ في تريبز، سيرو فيه 8 ملفات) · docs و planning (تريبز له docs/ الخاص) · deploy.sh (ليس نشراً على سيرفر بل `git add . && git push origin --all` — فخّ في مستودع آخر) · transit_dashboard (بانتظار قرار مصير backend-transit و dashboards/transit-web). ⚠️ لا يبني بعد — ثلاثة نواقص متوقعة ومقصودة: 1. `.env` و `lib/env/env.g.dart` غير متعقَّبين في سيرو (أسرار لكل مستأجر): كل تطبيق فلاتر يحتاج .env خاصاً ثم توليد env.g.dart بـ build_runner. 2. إعدادات Firebase (9 ملفات google-services.json و GoogleService-Info.plist) يستبعدها .gitignore تريبز — ولكل مستأجر مشروع Firebase خاص أصلاً. 3. apps/driver في سيرو يشير إلى `../../Intaleq/packages/get` خارج المستودع → يجب ضمّ الحزم داخله أسوة بـ apps/rider. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
9909d9b4c1
commit
4d8414c96b
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
/**
|
||||
* core/Security/BlindIndex.php
|
||||
*
|
||||
* فهرس أعمى للبحث فوق حقول مشفّرة.
|
||||
*
|
||||
* المشكلة: التشفير الآمن (AES-GCM) عشوائي — نفس النص ينتج تشفيراً مختلفاً في
|
||||
* كل مرة، فلا يمكن البحث بمقارنة النص المشفّر. والحل القديم (CBC بـ IV ثابت)
|
||||
* يجعل التشفير حتمياً فينجح البحث، لكنه يسرّب المساواة والبادئات المشتركة.
|
||||
*
|
||||
* الحل: نفصل التخزين عن البحث.
|
||||
* - التخزين: AES-GCM عشوائي (لا يسرّب شيئاً).
|
||||
* - البحث: عمود إضافي يحمل HMAC-SHA256 حتمياً للقيمة بعد تطبيعها.
|
||||
*
|
||||
* لماذا HMAC وليس sha256 عارياً؟ لأن مساحة أرقام الهواتف صغيرة (ملايين
|
||||
* قليلة) — جدول عكسي لكل الأرقام يُبنى في ثوانٍ. المفتاح السرّي (pepper)
|
||||
* المخزَّن في البيئة وحده يمنع ذلك، فمن يسرق قاعدة البيانات لا يملكه.
|
||||
*/
|
||||
|
||||
final class BlindIndex
|
||||
{
|
||||
private string $pepper;
|
||||
|
||||
public function __construct(?string $pepper = null)
|
||||
{
|
||||
$pepper = $pepper ?: (getenv('BLIND_INDEX_PEPPER') ?: '');
|
||||
if ($pepper === '') {
|
||||
throw new RuntimeException(
|
||||
'BLIND_INDEX_PEPPER is not set. Generate one with: openssl rand -hex 32'
|
||||
);
|
||||
}
|
||||
$this->pepper = $pepper;
|
||||
}
|
||||
|
||||
/**
|
||||
* يحسب الفهرس لقيمة داخل حقل محدد.
|
||||
*
|
||||
* $scope يشمل الجدول والحقل (مثل "driver.phone") عمداً: بدونه يكون فهرس
|
||||
* نفس الرقم متطابقاً في جدول السائقين والركاب، فيستطيع من يقرأ القاعدة
|
||||
* ربط الحسابات ببعضها دون فك أي تشفير.
|
||||
*/
|
||||
public function index(string $scope, ?string $value): ?string
|
||||
{
|
||||
$normalized = self::normalize($scope, $value);
|
||||
if ($normalized === null || $normalized === '') {
|
||||
return null;
|
||||
}
|
||||
return hash_hmac('sha256', $scope . ':' . $normalized, $this->pepper);
|
||||
}
|
||||
|
||||
/**
|
||||
* فهرس مبتور للبحث الجزئي (مثل الأسماء).
|
||||
*
|
||||
* البتر مقصود: يُنتج تطابقات كاذبة تُصفّى بعد فك التشفير، وهذه الضبابية
|
||||
* هي ما يمنع استخدام الفهرس نفسه في تحليل التكرارات.
|
||||
*/
|
||||
public function bucket(string $scope, ?string $value, int $length = 8): ?string
|
||||
{
|
||||
$full = $this->index($scope, $value);
|
||||
return $full === null ? null : substr($full, 0, $length);
|
||||
}
|
||||
|
||||
/**
|
||||
* التطبيع قبل الحساب — بدونه يُنتج 0791234567 و+962791234567 فهرسين
|
||||
* مختلفين ويفشل البحث.
|
||||
*/
|
||||
public static function normalize(string $scope, ?string $value): ?string
|
||||
{
|
||||
if ($value === null) return null;
|
||||
$value = trim($value);
|
||||
if ($value === '') return null;
|
||||
|
||||
if (str_contains($scope, 'phone')) {
|
||||
$digits = preg_replace('/\D+/', '', $value);
|
||||
// توحيد الصيغة المحلية والدولية على شكل واحد
|
||||
$digits = preg_replace('/^00/', '', $digits);
|
||||
if (str_starts_with($digits, '0')) {
|
||||
$cc = getenv('DEFAULT_COUNTRY_CODE') ?: '962';
|
||||
$digits = $cc . substr($digits, 1);
|
||||
}
|
||||
return $digits;
|
||||
}
|
||||
|
||||
if (str_contains($scope, 'email')) {
|
||||
return mb_strtolower($value, 'UTF-8');
|
||||
}
|
||||
|
||||
// الأسماء: توحيد حالة الأحرف والمسافات، وتوحيد أشكال الألف والياء
|
||||
// والتاء المربوطة العربية حتى لا يتوقف البحث على شكل الكتابة.
|
||||
$value = mb_strtolower($value, 'UTF-8');
|
||||
$value = preg_replace('/\s+/u', ' ', $value);
|
||||
$value = str_replace(
|
||||
['أ', 'إ', 'آ', 'ٱ', 'ى', 'ة', 'ؤ', 'ئ'],
|
||||
['ا', 'ا', 'ا', 'ا', 'ي', 'ه', 'و', 'ي'],
|
||||
$value
|
||||
);
|
||||
// إزالة التشكيل
|
||||
$value = preg_replace('/[\x{064B}-\x{0652}\x{0640}]/u', '', $value);
|
||||
return trim($value);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user