المصادقة
هذه الصفحة تشرح الوضع الحالي للمصادقة في شام باص من منظور داخلي. إذا تعارضت مع 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
refreshcookie-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.
- الطلبات التي تغيّر البيانات يجب أن ترسل قيمة الكوكي في الهيدر المناسب
- التطبيقات التي تعتمد
Authorizationbearer ولا تستخدم 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 من دون طباعة المعرّف الموجود في المفتاح.
أفضل الممارسات
للمطورين
- لا تنشر أسراراً أو أمثلة توكنات حقيقية في الوثائق
- اربط أي تغيير عقدي في المصادقة بتحديث الوثائق وtests
- تعامل مع cookies والجلسات كعقد أمني، لا كتفصيل واجهة
- سجل الأحداث الإدارية الحساسة في
admin_audit_log - فرّق دائماً بين الحالة المؤكدة والتخمين
للمراجعة الأمنية
- هل تغيّر route contract؟
- هل ما زالت cookies تنظف عند logout؟
- هل 2FA يكتب إلى السجل؟
- هل هناك fallback dev-only قد يتسرب خارج التطوير؟
- هل الصلاحيات تمنع العمليات الحساسة فعلاً؟