إنتقل إلى المحتوى الرئيسي

المصادقة

هذه الصفحة تشرح الوضع الحالي للمصادقة في شام باص من منظور داخلي. إذا تعارضت مع route handlers أو طبقة @shambus/auth فالأولوية للتنفيذ في المستودع.

نظرة عامة

تعتمد المنصة على أكثر من نمط:

  • العملاء: OTP عبر الهاتف أو Google للمسافرين فقط؛ الويب cookie-first والموبايل بجلسة Bearer مشفرة
  • لوحات الشركة: بريد إلكتروني وكلمة مرور وفق تدفق اللوحة الحالي
  • لوحة الإدارة: بريد إلكتروني وكلمة مرور مع 2FA عند الحاجة وRBAC
  • السائق: تدفق تشغيلي مرتبط بحساب السائق وحالة التطبيق

مصادقة العملاء

التدفق الحالي

sequenceDiagram
participant User as "Customer"
participant App as "Web/Mobile"
participant API as "Customer API"
participant OTP as "OTP Provider"

User->>App: إدخال رقم الهاتف
App->>API: POST /api/auth/customer/send-otp
API->>OTP: طلب إرسال الرمز
OTP->>User: SMS أو Telegram المحدد صراحة
User->>App: إدخال الرمز
App->>API: POST /api/auth/customer/verify-otp
API->>App: HttpOnly cookies + data.user

نقاط مهمة

  • send-otp يستخدم الحقل method
  • القنوات المعروضة للعملاء هي sms وtelegram; قيمة whatsapp القديمة تتحول إلى SMS بدون Meta
  • تيليجرام يستخدم بوت المنصة وجدول telegram_chat_links لربط رقم الهاتف مع Chat ID؛ يمكن أيضاً إرسال telegramChatId يدوياً كمسار احتياطي
  • verify-otp يعيد بيانات ضمن data
  • الجلسة الحديثة تعتمد على HttpOnly cookies
  • refresh cookie-first مع توافق خلفي لـ body

Google للمسافرين على الويب والموبايل

  • يستخدم الويب زر Google Identity Services الرسمي. يصدر الخادم nonce عشوائياً لكل flow ويحتفظ ببصمته فقط في cookie من نوع HttpOnly وSameSite؛ يجب أن يطابق claim داخل ID token قبل إصدار cookie جلسة المسافر.

  • يستخدم Android وiOS حزمة google_sign_in الأصلية ويمرران ID token إلى POST /api/auth/customer/google/mobile؛ لا تُفتح نافذة ويب مكتبية مضمّنة. يرفض عقد الموبايل ترويسات أصل المتصفح حتى لا يُستخدم كتجاوز لمسار nonce الخاص بالويب.

  • يستخدم Flutter Web/PWA زر GIS الرسمي وnonce نفسه، ثم يرسل الرمز إلى POST /api/auth/customer/google/pwa. يسمح CORS لهذا العقد فقط من app.shambus.com، ويعيد جلسة bearer لطبقة التخزين المشفر الموجودة في Flutter.

  • يتحقق الخادم عبر Google Auth Library من التوقيع وissuer وaudience والانتهاء وemail_verified. لا يثق باسم أو بريد يرسله العميل منفصلاً.

  • الجمهور القانوني الوحيد هو GOOGLE_OAUTH_WEB_CLIENT_ID. لا يوجد client secret في Flutter أو JavaScript أو في استجابة API.

  • يحل RPC خاص بـservice_role الحساب المرتبط عبر Google subject أو بريد موثق، ويمنع إعادة استخدام أي auth user مرتبط بإدارة أو شركة أو سائق. ربط Google من إعدادات الملف الشخصي يثبت أيضاً ملكية المسافر ويقفل subject والبريد لمنع السباق.

  • يصدر الخادم رمز magic-link أحادي الاستخدام ويستهلكه داخلياً للحصول على جلسة Supabase؛ لا يغيّر كلمة مرور المستخدم. يضاف البريد الموثق إلى auth user نفسه عند ترقية حساب هاتف قديم، وأي تعارض أو سباق يفشل مغلقاً.

  • لا يمنع غياب الهاتف نجاح Google. يطلب التطبيق هاتف E.164 وOTP فقط عند الحاجة التشغيلية ويحافظ على رابط Journey أو checkout النسبي حتى العودة.

  • لا يُفعّل إصدار حقيقي قبل تسجيل Web/iOS/Android OAuth clients، أصول shambus.com وapp.shambus.com، حزمة sy.shambus.app وبصمات توقيع Android الفعلية، ثم اختبار جهاز لكل منصة.

الإدارة و2FA

السلوك الحالي

  • تسجيل دخول لوحة الإدارة يعتمد أولاً على Supabase Auth بالبريد وكلمة المرور، ثم يتحقق من صف admin_users المرتبط عبر auth_user_id
  • بيئة التطوير المحلية تضمن حسابات admin@shambus.com وsupport@shambus.com وmanager@shambus.com عبر ترحيلات قاعدة البيانات
  • قد يعيد login الإداري requires2FA
  • صفحة الإعدادات الإدارية تدير التسجيل، التفعيل، رموز الاسترداد، والتعطيل
  • يتم تسجيل نشاط 2FA والأحداث المرتبطة به في السجل الإداري
  • أسرار TOTP للمشرف والسائق مشفرة في قاعدة البيانات بصيغة totp:v1 ومرتبطة بمعرف الحساب؛ يفكها الخادم فقط لحظة التحقق ولا يعيدها في API بعد التسجيل

عقد جلسة غلاف الإدارة

لا يعتمد غلاف لوحة الإدارة على قراءة admin_users مباشرةً من المتصفح. قبل تركيب أي صفحة محمية، يستدعي GET /api/auth/me، وهو يمر عبر الحارس نفسه الذي تستخدمه واجهات الإدارة البرمجية ويتحقق من هوية Supabase ومنع التوكن وإثبات العامل الثاني والصلاحيات معاً.

  • تعيد جلسة Auth المنتهية المستخدم إلى الدخول برسالة انتهاء الجلسة.
  • يعيد إثبات 2FA المنتهي المستخدم إلى تدفق الدخول وإدخال TOTP من جديد، ولا يترك غلافاً إدارياً ناقصاً حول صفحة تفشل طلباتها بـ403.
  • يعيد endpoint إلى المتصفح ملفاً عاماً محدوداً إلى id وname وemail وrole والصلاحيات. لا تُسلسل أسرار TOTP أو hashes رموز الاسترداد أو حقول الجلسة الداخلية.
  • يمر تسجيل الخروج الطبيعي عبر endpoint الخادم لسحب إثبات العامل الثاني ومنع التوكن، مع تنظيف محلي دفاعي إذا تعذّر الوصول إلى الخادم.

ما الذي يجب مراقبته؟

  • محاولات 2FA الفاشلة
  • إعادة إنشاء backup codes
  • تعطيل 2FA
  • تغيرات الصلاحيات الإدارية

الشركة والصلاحيات

ضوابط الشركة

  • الشركة الموقوفة أو غير المفعلة يجب ألا تمر إلى العمليات الحساسة
  • مسارات التعديل تعتمد على فحوص access/role المناسبة
  • لا يجوز الاكتفاء برسالة واجهة فقط في المسارات الحساسة

أدوار شائعة

الدورالاستخدام
OWNERتحكم كامل بالحساب
ADMINتشغيل موسع وإعدادات
STAFFمهام يومية بحسب التفعيل
DRIVERمهام تشغيلية في السطح المناسب

الجلسات والكوكيز

مبادئ أساسية

  • استخدام HttpOnly cookies حيثما كان ذلك هو العقد الحالي
  • تنظيف cookies عند logout
  • منع إعادة استخدام التوكنات عند الإمكان
  • عدم الاعتماد على التخزين المحلي المكشوف للجلسات الحساسة في الويب

CSRF

يستخدم الويب حالياً نمط Double Submit Cookie عبر @shambus/auth/csrf.

  • الطلبات التي تغيّر البيانات يجب أن ترسل قيمة الكوكي في الهيدر المناسب
  • التطبيقات التي تعتمد Authorization bearer ولا تستخدم cookies لا تمر عبر نفس فحص CSRF
  • بوابتا الشركة والإدارة تنشئان cookie CSRF آمنة تلقائياً عندما تكون الجلسة المستعادة صالحة لكن cookie مفقودة، من دون تدوير قيمة موجودة في كل تنقل.
  • يجوز لمسار POST عام إعفاء CSRF فقط عندما يكون عملية قراءة موثقة لا تغيّر أي حالة، مثل بحث Journey المركب. يبقى التحقق من المدخلات وحد الطلبات مطلوبين.

Rate Limiting

أمثلة على النقاط الحساسة

المسارنوع الحماية
send-otpحد حسب IP وحسب الهاتف
verify-otpحد حسب IP وحسب الهاتف
Google mobileحد حسب IP وحسب subject مجهّل
login الإداريحد حسب المستخدم/IP
refresh/logoutحد API عام أو خاص

تستخدم نقاط دخول لوحة الشركة ولوحة الإدارة وتطبيق السائق حدين متراكبين خلال نافذة 15 دقيقة:

  • 5 محاولات لكل حساب، بمفتاح يتكون من نطاق البوابة وبصمة SHA-256 للبريد الموحّد؛ لا يُحفظ البريد نفسه في Redis.
  • 30 محاولة لكل عنوان IP عبر جميع الحسابات، لمنع الإساءة من دون أن تجعل خمسة أخطاء من مستخدم واحد تحجب بقية موظفي شركة تعمل خلف NAT مشترك.

يجب تطبيق الحدين معاً على أي نقطة دخول جديدة؛ الحد الأعلى حسب IP ليس بديلاً عن الحد الأدق حسب الحساب.

تضيف بوابة Kong طبقة حماية مستقلة لمسار Supabase Auth المباشر: 5 طلبات/ثانية و60 طلباً/دقيقة و600 طلب/ساعة لكل عنوان مصدر محلول. يجب إبقاء limit_by: ip صريحاً؛ لأن Key Auth يربط المفتاح العام بـconsumer anon، واستخدام القيمة الافتراضية consumer يجعل جميع الركاب والموظفين والمشرفين يتنافسون خطأً على عداد واحد. يضبط Compose عناوين البروكسي الخاصة الموثوقة و X-Forwarded-For العودي حتى لا يتحول عنوان حاوية Traefik إلى هوية كل مستخدم. لا تستبدل حدود الحساب/IP في التطبيق بهذه الطبقة؛ الاثنتان متراكمتان ومقصودتان.

تستدعي تطبيقات Next.js تهيئة Redis من src/instrumentation.ts. يجب إبقاء الملف داخل src/ لأن التطبيقات الثلاثة تستخدم هذا التخطيط؛ وضعه في جذر التطبيق يمنع Next.js من تضمينه في حزمة الإنتاج، فتعود العدادات إلى ذاكرة كل حاوية وتفقد الاتساق عند التوسع أو النشر. تحتفظ خدمة المعدل أيضاً بحالتها في globalThis وتجري تهيئة Redis دفاعية عند أول فحص؛ فهذا يمنع انفصال singleton الخاص بحزمة instrumentation عن singleton حزم route handlers. بعد النشر، تحقق بطلب محدود من ظهور مفتاح ratelimit:* في Redis من دون طباعة المعرّف الموجود في المفتاح.

أفضل الممارسات

للمطورين

  1. لا تنشر أسراراً أو أمثلة توكنات حقيقية في الوثائق
  2. اربط أي تغيير عقدي في المصادقة بتحديث الوثائق وtests
  3. تعامل مع cookies والجلسات كعقد أمني، لا كتفصيل واجهة
  4. سجل الأحداث الإدارية الحساسة في admin_audit_log
  5. فرّق دائماً بين الحالة المؤكدة والتخمين

للمراجعة الأمنية

  • هل تغيّر route contract؟
  • هل ما زالت cookies تنظف عند logout؟
  • هل 2FA يكتب إلى السجل؟
  • هل هناك fallback dev-only قد يتسرب خارج التطوير؟
  • هل الصلاحيات تمنع العمليات الحساسة فعلاً؟