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

تسجيل الدخول برمز OTP

الخلفية​

على Shukah يسجّل التطبيق الدخول باستدعاءين. يرسل GET /maeda/api/reset-otp رمزًا عبر SMS، ويستبدل GET /maeda/api/verify-otp الرمز بـ token. واستدعاء reset-otp نفسه يسجّل أيضًا عميلًا جديدًا. تضم خدمة المصادقة (auth service) الـ routes الاثنين بأسماء الـ query نفسها عند Shukah، لذا فالتحوّل في معظمه تغيير في base URL وبادئة المسار. وثلاثة أمور أخرى تستحق العناية: يُرجع verify-otp JWT من Locate بدل token من Shukah، وأخطاؤه لها body مختلف، وهو يتجاهل fcm_token وplatform اللذين يرسلهما التطبيق. كما أن reset-otp يصوغ خطأ الهاتف المفقود بعبارة مختلفة.

الميزة​

يستخدم تسجيل الدخول وإنشاء الحساب الاستدعاءين نفسيهما.

  1. يستدعي التطبيق reset-otp مع رقم الهاتف. إذا كان لعميل نشط (active) هذا الهاتف، ترسل الخدمة رمزًا من 4 أرقام عبر SMS وتجيب {"status":true}.
  2. إذا لم يكن لأي عميل نشط هذا الهاتف ولم يحمل الاستدعاء حقول التسجيل، يجيب reset-otp بـ 400 مع Last Name cannot be blank.. وهذه الرسالة هي إشارة التطبيق لفتح شاشة التسجيل. ثم يستدعي التطبيق reset-otp مجددًا مع حقول التسجيل، فتنشئ الخدمة العميل وترسل الرمز.
  3. يستدعي التطبيق verify-otp مع الهاتف والرمز. فيحصل على JWT من Locate وعلى الملف الشخصي للعميل، ويرسل الـ JWT في Authorization: Bearer <token> مع كل استدعاء لاحق.

لا تنقل تسجيل الدخول إلى Locate بعد. فتسجيل الدخول في Locate يُرجع JWT فقط، وترفض routes النقاط في order-v2 الـ JWT اليوم. وإلى أن يقبله order-v2، واصل تسجيل الدخول على Shukah، واحصل على الـ JWT عبر exchange-token (target_platform: locate)، واحتفظ بالـ tokenين: الـ JWT لخدمة المصادقة، وtoken Shukah لـ order-v2 (انظر قائمة التحويل).

في نسخة التطبيق اللحظية بتاريخ 2026-02-26، يرسل تسجيل الدخول الهاتف بعد حذف علامة +، ويضيف التسجيل الأسماء والبريد الإلكتروني، واختياريًا sex وbirth_date وreceive_offers. ويفتح التطبيق شاشة التسجيل عندما يجيب reset-otp بـ 400 مع رسالة تتضمن Last Name، ويخزّن access_token الذي يُرجعه verify-otp.

قواعد الرمز:

القاعدةالقيمة
الطول4 أرقام. ويمكن أن يبدأ بـ 0.
مدة الصلاحية3 دقائق. ويمكن استخدامه مرة واحدة.
إعادة الإرسالما دام هناك رمز معلّق، فإن استدعاء reset-otp مجددًا لا يرسل شيئًا ويظل يجيب {"status":true}. ولا يمكن إصدار رمز جديد إلا بعد انتهاء صلاحية القديم أو استخدامه.
المحاولاتلا حد للرموز الخاطئة ولا حد لمعدل الطلبات (rate limit) على أي من الاستدعاءين.
وضع الاختباريحصل حساب وضع الاختبار دائمًا على الرمز 2022 ولا يُرسل SMS. أما أي أرقام على dev هي في وضع الاختبار: غير موثّق بعد.

GET /api/v1/clients/reset-otp​

يستبدل ShukahGET /maeda/api/reset-otp✅ تم الاستبدال
استبدال مباشر0 breaking · 3 للمراقبة
  • 🟡 خطأ 400 لغياب الهاتف بالإنجليزية (Yii) على Locate وبالعربية (Laravel) على Shukah؛ ويعرض التطبيق message لذا يتغير النص.
  • 🟡 يحصل الهاتف غير المعروف على 400 "Last Name cannot be blank." على Locate (بحسب الكود)؛ ونص Shukah الحالي غير مسجَّل.
  • 🟡 لا يفتح التطبيق التسجيل إلا إذا احتوت رسالة 400 على "Last Name"؛ تأكد من اتفاق Shukah وLocate.

route واحد يخدم تسجيل الدخول والتسجيل معًا، كما في Shukah؛ وفي Locate يحصل الهاتف غير المعروف على 400 "Last Name cannot be blank." (وهي إشارة للتطبيق لفتح التسجيل؛ بحسب الكود، أما نص Shukah الحالي فغير موثّق) ولا يُقرأ receive_offers.

الطلب (Request)​

لا حاجة إلى token. وعندما يعود الهاتف إلى عميل نشط تُتجاهل حقول التسجيل.

الحقلالموضعالنوعمطلوبالقيودالوصف
contact_phonequerystringنعمتُحذف المسافات. ويُطابَق مع الهاتف المخزَّن بوجود + في البداية أو بدونه (+966… يساوي 966…، لكن 05… لا يساوي +9665…). لا فحص للطول أو الصيغة. الفارغ أو المفقود يعطي 400.الهاتف المطلوب تسجيل الدخول به أو تسجيله.
last_namequerystringللتسجيل فقطمطلوب عندما لا يكون لأي عميل نشط هذا الهاتف. الفارغ يعطي 400 مع Last Name cannot be blank. (يُفحص أولًا). بحد أقصى 100 حرف.اسم العائلة للعميل الجديد.
first_namequerystringللتسجيل فقطالفارغ يعطي 400 مع First Name cannot be blank. (يُفحص ثانيًا). بحد أقصى 100 حرف.الاسم الأول للعميل الجديد.
email_addressquerystringللتسجيل فقطالفارغ يعطي 400 مع Email Address cannot be blank. (يُفحص ثالثًا). لا فحص لصيغة البريد.البريد الإلكتروني للعميل الجديد.
birth_datequerystringلايُقرأ كتاريخ ويُخزَّن بالصيغة YYYY-MM-DD. والقيمة التي ليست تاريخًا تُخزَّن فارغة بصمت.تاريخ ميلاد العميل الجديد.
sexquery1 أو 2لا1 ذكر و2 أنثى. وأي قيمة أخرى تُخزَّن «غير معروف».جنس العميل الجديد.
languagequerystringلاتُخزَّن الجزء الذي قبل - بأحرف صغيرة (ar-SA تصبح ar). والمفقودة تصبح ar. ولا تغيّر لغة SMS، التي هي دائمًا العربية.لغة العميل الجديد.
أي مفتاح query أو header آخرquery، header—لايُتجاهل، مثل key. ولا يُقرأ receive_offers أيضًا: فالعميل الجديد يبدأ وهو معطَّل.—

الاستجابة (Response)​

200 بالـ body نفسه لتسجيل الدخول وللتسجيل:

الحقلالنوعيقبل nullالصيغة / القيودالوصف
statusbooleanلادائمًا true.أُصدر رمز، أو يوجد رمز معلّق بالفعل. ولا يؤكد أن SMS قد أُرسلت.
{
"status": true
}

التُقط على dev، 2026-10-02، لهاتف معروف، على Locate وعلى Shukah: وأجابتا بهذا الـ body. وأرسل الاستدعاء رسالة SMS واحدة. والـ body نفسه موجود في docs/api/client-auth-and-address-book.md §1 (locate-authentication-service@6757aa4، آخر تغيير للوثيقة 2026-07-27).

الأخطاء​

HTTPBodyمتى
400{"name":"Bad Request","message":"Phone number cannot be blank.","code":0,"status":400,"type":"yii\\web\\BadRequestHttpException"}contact_phone مفقود أو فارغ.
400الشكل نفسه، مع ضبط message على Last Name cannot be blank. أو First Name cannot be blank. أو Email Address cannot be blank..لا عميل نشط بهذا الهاتف، وأول حقل تسجيل فارغ (بهذا الترتيب) هو المسمّى. والاستدعاء المجرد لتسجيل الدخول بهاتف غير معروف يحصل على Last Name cannot be blank..
500{"status":500,"message":"Internal server error"}فشل غير متوقع، مثل اسم أطول من 100 حرف عند التسجيل، أو خطأ في قاعدة البيانات، أو بوابة SMS لا تعطي استجابة HTTP. لم يُلتقط. وخارج production تكون الرسالة نص الخطأ الخام ويُضاف حقل stack.

التُقط body الخاص بـ Phone number cannot be blank. على dev في 2026-10-01 (استدعاء دون أي parameters). أما body الخاص بـ Last Name cannot be blank. فمن docs/api/client-auth-and-address-book.md §1 (2026-07-27): فجولة الالتقاط لم تجرِ أي استدعاء برقم هاتف، لأن الاستدعاء قد يرسل SMS.

جرّبه​

الاستدعاء الناجح يرسل SMS حقيقية ما لم يكن الرقم لحساب في وضع الاختبار. استخدم رقمًا في وضع الاختبار تتحكم به. أي الأرقام في وضع الاختبار على dev: غير موثّق بعد.

export AUTH_BASE_URL=https://dev.authentication.locate.sa
export PHONE='<test-mode-phone>' # placeholder: replace it with a test-mode number
curl -s -G "$AUTH_BASE_URL/api/v1/clients/reset-otp" --data-urlencode "contact_phone=$PHONE"

المتوقع: 200 و{"status":true}. وبلا هاتف تحصل على 400 المبيّن تحت «الأخطاء».

الفروق عن Shukah​

الجانبShukahLocateالأثر على التطبيق
URLGET $SHUKAH_BASE_URL/maeda/api/reset-otpGET $AUTH_BASE_URL/api/v1/clients/reset-otp. تتجاهل Locate key وكل مفتاح query مجهول آخر وكل header.غيّر base URL والمسار. ويمكن للتطبيق مواصلة إرسال key وheaders الخاصة به.
خطأ الهاتف المفقود{"name":"Bad Request","message":"حقل contact phone مطلوب.","code":0,"status":400,"type":"Illuminate\\Validation\\ValidationException"} (التُقط على dev، 2026-10-01): رسالة بالعربية وtype من Laravel.{"name":"Bad Request","message":"Phone number cannot be blank.","code":0,"status":400,"type":"yii\\web\\BadRequestHttpException"} (التُقط على dev، 2026-10-01): رسالة بالإنجليزية وtype من Yii.المفاتيح نفسها، لكن النص وtype يختلفان. لا تعتمد على هذا النص في المطابقة.
هاتف غير معروف، بلا حقول تسجيل400 مع Last Name cannot be blank. في body بنمط Yii، في التقاط يوليو 2026 المحفوظ في الوثيقة المسودة لخدمة المصادقة (انظر Sources تحت «التفاصيل»). نص Shukah اليوم: غير موثّق بعد.400 مع Last Name cannot be blank. في body بنمط Yii.تستمر إشارة التسجيل في العمل إن كانت Shukah ما زالت ترسل النص نفسه اليوم. أكّد نص Shukah الحالي قبل الاعتماد عليه.
body النجاح{"status":true} (التُقط على dev، 2026-10-02).{"status":true} (التُقط على dev، 2026-10-02).لا شيء.

GET /api/v1/clients/verify-otp​

يستبدل ShukahGET /maeda/api/verify-otp✅ تم الاستبدال
يحتاج تعديل التطبيق1 breaking · 3 للمراقبة
  • 🔴 لا تنقل تسجيل الدخول بعد: يُرجع تسجيل الدخول في Locate JWT فقط، وترفضه routes النقاط في order-v2 اليوم.
  • 🟡 body الـ 400 عند الرمز الخاطئ هو {status,message}؛ وتضيف Shukah الحقول name/code/type (Laravel)؛ ويقرأ التطبيق message فقط.
  • 🟡 يُتجاهل fcm_token وplatform على Locate؛ ولا يظهر في استجابة Shukah ما تفعله بهما.
  • 🟡 استدعاء ناجح على dev (2026-10-02): نفس {access_token,data} وملف شخصي بـ 13 مفتاحًا على الجانبين؛ وتبقى الجلسات السابقة صالحة.

يُرجع access_token وملفًا شخصيًا في data، حيث الـ token هو JWT من Locate (وbody النجاح في Shukah له الشكل نفسه، التُقط في 2026-10-02)، ويُتجاهل fcm_token وplatform.

الطلب (Request)​

لا حاجة إلى token.

الحقلالموضعالنوعمطلوبالقيودالوصف
contact_phonequerystringنعمتُحذف المسافات. ويُطابَق مع الهاتف الذي أُرسل إليه الرمز، بوجود + في البداية أو بدونه. المفقود يعطي 400 نفسه الذي يعطيه الرمز الخاطئ.الهاتف المستخدم في reset-otp.
codequerystringنعميُقارَن كنص مع الرمز المكوَّن من 4 أرقام، لذا فالصفر في البداية مهم.الرمز الوارد في SMS.
language وplatform وfcm_token وأي مفتاح query آخرquerystringلايُتجاهل.—

الاستجابة (Response)​

200 مع الـ token والملف الشخصي:

الحقلالنوعيقبل nullالصيغة / القيودالوصف
access_tokenstringلاJWT من Locate: ثلاثة أجزاء تفصلها نقاط، موقَّع بـ HS256. لا تنتهي صلاحيته أبدًا.أرسله في Authorization: Bearer <access_token> مع كل استدعاء لاحق.
dataobjectلاالملف الشخصي للعميل: الحقول الـ 13 المدرجة تحت التحقق من الـ token.العميل المسجَّل الدخول.
{
"access_token": "<locate-jwt>",
"data": {
"client_id": 356259,
"odoo_id": "1342170",
"contact_phone": "+966500000000",
"status": "active",
"email_address": "user@example.com",
"first_name": "Test",
"last_name": "User",
"sex": 1,
"birth_date": "1990-01-01",
"receive_offers": 0,
"push_enabled": 0,
"is_food_truck_user": 1,
"is_cod_disabled": 0
}
}

الشكل من docs/api/client-auth-and-address-book.md §2 (locate-authentication-service@6757aa4، آخر تغيير للوثيقة 2026-07-27)، واستُدعي مباشرة على dev في 2026-10-02 (200، بالمفاتيح والقيم نفسها كما أعلاه؛ انظر «الفروق عن Shukah»). أُخفيت قيمة الـ token عند الالتقاط، فلم يُسجَّل هنا نوعه: وادعاء JWT مأخوذ من الكود. وقيم الملف الشخصي هي القيم التوضيحية المستخدمة في الموقع.

الأخطاء​

HTTPBodyمتى
400{"status":400,"message":"Sorry, but your verification code is incorrect"}لا يوجد رمز غير مستخدم وغير منتهي يطابق الهاتف والرمز (خاطئ أو منتهي أو مستخدم من قبل أو نقص في parameter)، أو لا عميل نشط بهذا الهاتف. رسالة واحدة تشمل كل ذلك، عن قصد، حتى لا يكشف الاستدعاء أي الهواتف لها حساب.
400الشكل نفسه، مع نص الخطأ الخام في message.أي فشل آخر داخل الـ handler، مثل خطأ في قاعدة البيانات أو فشل كتابة الـ token. لم يُلتقط.

التُقط أول body على dev في 2026-10-01 (استدعاء دون أي parameters)، ومرة أخرى في 2026-10-02 برمز خاطئ. وهو ليس بنمط Yii، بخلاف أخطاء reset-otp.

جرّبه​

الاستدعاء الناجح يستهلك الرمز وينشئ token جديدًا من Locate. وتبقى الـ tokens السابقة صالحة.

export AUTH_BASE_URL=https://dev.authentication.locate.sa
export PHONE='<test-mode-phone>' # the number you used in reset-otp
export CODE='<4-digit-code>' # 2022 for a test-mode account
curl -s -G "$AUTH_BASE_URL/api/v1/clients/verify-otp" --data-urlencode "contact_phone=$PHONE" --data-urlencode "code=$CODE"

المتوقع: 200 مع access_token وdata. والرمز الخاطئ أو المنتهي يعطي 400 المبيّن تحت «الأخطاء».

الفروق عن Shukah​

الجانبShukahLocateالأثر على التطبيق
URLGET $SHUKAH_BASE_URL/maeda/api/verify-otpGET $AUTH_BASE_URL/api/v1/clients/verify-otpغيّر base URL والمسار.
الـ Tokentoken معتم (opaque) من Shukah.JWT من Locate في access_token.كل استدعاء لاحق إلى /api/v1/clients/* يحتاج هذا الـ JWT، لكن routes النقاط في order-v2 ترفضه اليوم، لذا واصل تسجيل الدخول على Shukah حالياً.
مفاتيح query إضافيةيرسل التطبيق أيضًا fcm_token (token الـ push الخاص به، ويُحذف عندما يتعذّر على التطبيق الحصول عليه) وplatform (Android أو iOS أو Huawei). ما تفعله Shukah بهما: غير موثّق بعد.تقرأ Locate contact_phone وcode فقط، فتُسقط fcm_token وplatform.يظل الاستدعاء يعمل. وإن كانت Shukah تستخدم أيًّا من القيمتين فإن ذلك يضيع على Locate: أكّد ما كانت تفعله Shukah بهما.
خطأ الرمز الخاطئ{"name":"Bad Request","message":"Sorry, but your verification code is incorrect","code":0,"status":400,"type":"Symfony\\Component\\HttpKernel\\Exception\\HttpException"} (التُقط على dev، 2026-10-02). وكان في التقاط يوليو 2026 المحفوظ في الوثيقة المسودة لخدمة المصادقة الرسالة نفسها مع type بنمط Yii هو yii\\web\\BadRequestHttpException (انظر Sources تحت «التفاصيل»).{"status":400,"message":"Sorry, but your verification code is incorrect"} (التُقط على dev، 2026-10-01 و2026-10-02).message وstatus موجودان في الاثنين، بالنص نفسه. أما name وcode وtype فقد اختفت، فلا تقرأها.
body النجاح200 {access_token, data}، حيث data هو الملف الشخصي ذو الحقول الـ 13 (التُقط على dev، 2026-10-02، بالهاتف والرمز نفسيهما كما في Locate).200 {access_token, data}، حيث data هو الحقول الـ 13 نفسها بقيم متساوية (التُقط على dev، 2026-10-02).الشكل والملف الشخصي متطابقان، فلا يحتاج تحليل التطبيق إلى تغيير. أُخفيت قيمة access_token عند الالتقاط، فلم يُسجَّل هنا نوع الـ token على كل جانب (صف Token مأخوذ من الكود). ولم ينهِ تسجيل الدخول الجديد الجلسات السابقة على أي من الجانبين.

تنبيه​

  • استدعاء reset-otp ثانٍ خلال 3 دقائق لا يرسل SMS جديدة لكنه يظل يجيب {"status":true}، فلا تعني true أن SMS أُرسلت.
  • العميل غير النشط (موقوف أو محظور أو معلّق أو منتهٍ) يُعامل كهاتف غير معروف، فيُرسَل إلى التسجيل.
  • أجسام الأخطاء (error bodies) تختلف: أخطاء reset-otp بنمط Yii (name وmessage وcode وstatus وtype)، أما أخطاء verify-otp فهي status وmessage فقط.

التفاصيل (للـ backend)​

فُحص dev فقط: الـ host https://dev.authentication.locate.sa والبادئة /api/v1. أما الـ host والبادئة في UAT وproduction: غير موثّق بعد. لا يعمل dev بـ NODE_ENV=production: فأجسام أخطائه تحمل حقل stack (التُقط في 2026-10-01 على أخطاء address-book وprofile وsetting)، والـ handler لا يضيف stack إلا خارج production (src/middlewares/error-handler.js:12-35).

قواعد OTP​

القاعدةالحقيقةالمصدر
الطول4 محارف، أرقام عشرية، وقد تبدأ بـ 0: Math.random().toString().slice(2, 6).src/services/client-otp.js:7-8
الصلاحية3 دقائق من لحظة الإنشاء. ويشترط verify أن يكون expired_at الآن أو بعده.src/services/client-otp.js:5,37؛ src/controllers/client/verify-otp/handler.js:27
الاستخدام مرة واحدةيضبط verify القيمة verified: true وverified_at، ولا يطابق الرمز المستخدم بعد ذلك.src/controllers/client/verify-otp/handler.js:26,38-41
إعادة الإرسالإذا وُجد رمز غير مُتحقَّق منه وغير منتهٍ للهاتف المخزَّن، فلا يُنشأ شيء ولا يُرسل شيء وتظل الإجابة {"status":true}.src/services/client-otp.js:20-23
المحاولات وحدود المعدللا شيء: لا عدّاد ولا قفل ولا محدِّد معدل (rate limiter).docs/plans/2026-07-28-k6-load-test-suite.md:9
الرمز الثابت 2022، حساب وضع الاختبارaccount_mode بقيمة TEST يعطي 2022 ولا SMS.src/services/client-otp.js:7-8,40-42؛ src/shared/constant/user.js:27-30
الرمز الثابت 2022، هاتفانالهاتف المخزَّن الذي يساوي تمامًا +966500000000 أو 966500000000 يحصل على 2022 في أي وضع، وتُرسل SMS ما لم يكن الحساب في وضع الاختبار. أُضيف في 2026-07-14 (commit 7aad3af). والهاتف النموذجي المستخدم في هذه الصفحة أحد هذين.src/services/client-otp.js:25-27
كيف يُضبط وضع الاختبارالقيمة الافتراضية لـ account_mode هي STANDARD. ولا يضبطه أي API: بل يُكتب مباشرة في قاعدة البيانات.src/models/client.js:87-90؛ docs/plans/2026-07-28-k6-load-test-suite.md:18
أرقام الاختبارلا شيء موثّق لـ dev. تحجز مجموعة اختبار الحمل (load test) هواتف وضع الاختبار المكوَّنة من 9665000 يليها ستة أرقام (معرّفات العملاء من 9,000,000,000)، وتُزرع مباشرة في قاعدة البيانات. وهل توجد حسابات كهذه على قاعدة بيانات dev المشتركة: غير موثّق بعد.docs/plans/2026-07-28-k6-load-test-suite.md:19,33-40؛ k6/seed/seed-test-clients.js:23-24,40-58؛ k6/README.md:13-23
البحث عن الحسابيجد reset-otp وverify-otp والبحث عن العميل (client lookup) العميلَ بالهاتف المخزَّن مع + اختيارية، غير محذوف، بحالة ACTIVE، بأحدث client_id أولًا، دون ذاكرة مؤقتة (uncached). والعميل غير النشط غير مرئي لهذه الثلاثة.src/controllers/client/reset-otp/handler.js:24-30؛ src/shared/helpers/filter.js:1-4؛ docs/api/client-auth-and-address-book.md:52-54
SMSPOST ${SMS_API}/v1/messages مع Bearer ${SMS_TOKEN}. النص بالعربية مهما كانت قيمة language. والمستلم 0XXXXXXXXX يصبح 966XXXXXXXXX، وإلا أُرسل الهاتف المخزَّن كرقم. وتُحفظ استجابة البوابة في صف OTP.src/services/client-otp.js:11-14,30,41؛ src/shared/helpers/sms.js:4-24
التسجيل في logيُكتب الرمز المولَّد في log الخادم.src/services/client-otp.js:29

التسجيل عبر reset-otp​

عندما لا يكون لأي عميل نشط هذا الهاتف وتكون الحقول الثلاثة المطلوبة موجودة، ينشئ الـ handler عميلًا ثم يصدر الرمز (src/controllers/client/reset-otp/handler.js:48-73).

  • يحصل العميل الجديد على client_id = 1,000,000,000 + counter، وsource_system وchange_origin مضبوطين على locate، وfrom_locate: true، ومعرّفات Shukah فارغة، وclient_language، وقيم النموذج الافتراضية: الحالة ACTIVE، والوضع STANDARD، والـ push مفعّل، والعروض معطّلة (src/controllers/client/reset-otp/handler.js:51-69، src/models/counter.js:7,17-24، src/models/client.js:83-90,110-111). وتُخزَّن الأسماء بعد حذف المسافات الزائدة ويسمح النموذج بـ 100 حرف لكل منها (src/models/client.js:45-52).
  • بعد إصدار الرمز، تدفع الخدمة العميل إلى Shukah: POST {SHUKAH_HOST}/maeda/client، وهي عملية upsert بحسب الهاتف. وهي بذل أقصى جهد (best effort) ويُنتظر إتمامها، وتخزّن المعرّف الذي تُرجعه Shukah بوصفه shukah_id. ولا تفعل شيئًا عندما تكون المزامنة الصادرة (outbound sync) معطّلة أو لا يكون الـ host أو token المسؤول مضبوطًا (src/controllers/client/reset-otp/handler.js:71-73، src/services/shukah-sync.js:21-30,184-234).
  • الهاتف غير النشط يتصرف كهاتف غير معروف. ولا يعلن النموذج أي فهرس فريد (unique index) على phone، فمع كل حقول التسجيل سيُدخل الـ handler عميلًا ثانيًا بالهاتف نفسه، وسيفوز ذلك العميل في البحث (أعلى client_id). قراءة شيفرة، لم تُختبر مباشرة. وهل هذا مقصود: غير موثّق بعد (src/models/client.js:147-156).
  • ينتظر الطلب بوابة SMS (await sendMessage، بلا مهلة). واستجابة البوابة بخطأ HTTP تُحفظ في صف OTP وتُتجاهل، فيظل الرد {"status":true}. أما فشل البوابة دون أي استجابة HTTP فيُلقي استثناءً داخل catch ويعطي 500، بعد أن يكون صف OTP قد أُنشئ بالفعل، فلا تُرسل إعادة المحاولة خلال 3 دقائق شيئًا (src/shared/helpers/sms.js:18-23؛ قراءة شيفرة).

التحقق من الرمز​

ترتيب العمليات (src/controllers/client/verify-otp/handler.js:20-46): إيجاد الرمز، وإيجاد العميل النشط، وإنشاء JWT، وإدراج صف token_bridge، ودفع الـ JWT إلى Shukah، وتعليم الرمز بأنه مستخدم، ثم الرد. ولا يُستهلك الرمز إلا بعد انتهاء كتابة الـ token والدفع.

  • شكل النجاح {access_token, data} مثبَّت باختبار وحدة (unit test) (src/controllers/client/__tests__/verify-otp.test.js:77-94).
  • كل نجاح ينشئ token جديدًا. وتبقى الـ tokens السابقة صالحة لأنه لا شيء يُبطلها (src/shared/helpers/jwt.js:4-10). أُكّد مباشرة على dev في 2026-10-02: بعد تسجيل دخول جديد على كل جانب، ظل الـ tokens اللذان صدرا سابقًا يعملان.
  • لصف token_bridge القيمة source_system: 'merchant'، ولا يحمل الـ token الخام إلا عندما لا يكون NODE_ENV production (src/services/merchant-token-bridge.js:6-16).
  • الدفع إلى Shukah هو POST {SHUKAH_HOST}/maeda/client/add-token مع {access_token, client_id: <shukah_id>}، بذل أقصى جهد ويُنتظر إتمامه. ولا يفعل شيئًا عندما لا يكون للعميل shukah_id أو تكون المزامنة معطّلة (src/services/shukah-sync.js:13-75). وبهذه الطريقة تتعرف Shukah على JWTs من Locate (docs/plans/2026-07-16-shukah-token-outbound-push.md:10-28). وهل الدفع مفعّل على dev، وأي واجهات من Shukah تقبل JWT من Locate بعده: غير موثّق بعد.
  • أي استثناء في الـ handler (خطأ في قاعدة البيانات، أو فشل كتابة الـ token، أو تكرار hash للـ token) يُلتقط ويُجاب عنه بـ 400 مع الرسالة الخام (src/controllers/client/verify-otp/handler.js:47-53؛ قراءة شيفرة).
  • استدعاءا verify للعميل نفسه في الثانية نفسها سيُنشئان JWT متطابقًا بايتًا ببايت ويصطدمان بفهرس token_hash الفريد، فينتج 400 المذكور. قراءة شيفرة وفحص حتمية (determinism) دون اتصال، لم يُعاد إنتاجه: هل يحدث على dev: غير موثّق بعد.
  • لا يوجد عدّاد محاولات: فيمكن تجربة الرمز المكوَّن من 4 أرقام مرارًا خلال دقائقه الثلاث.

حيث تختلف الوثائق عن الشيفرة (القول للشيفرة)​

  • يقول مخطط التدفق الختامي في وثيقة الخدمة إن verify-otp يُرجع {id, token} (docs/api/client-auth-and-address-book.md:542). أما الشيفرة، ووثيقة الخدمة نفسها في الأسطر 110-131، فتقولان {access_token, data}.
  • تقول وثيقة الخدمة إن verify «يحذف كل رموز OTP للهاتف» (docs/api/client-auth-and-address-book.md:135). أما الشيفرة فتعلّم الرمز المطابق وحده بأنه مستخدم (commit a5a5315، 2026-07-17).
  • تقول وثيقة تصميم إن الرمز الخاطئ يعطي 400 بنمط Yii (docs/plans/2026-07-06-shukah-auth-api-local.md:101-104). أما الشيفرة ووثيقة الـ API فتعطيان {"status":400,"message":...}.
  • رمز وضع الاختبار هو 2022 في الشيفرة. أما اختبار الوحدة (src/services/__tests__/client-otp.test.js:6) ووثيقة تصميم (docs/plans/2026-07-06-shukah-auth-api-local.md:155,213) فتقولان 8888، وهذا الاختبار يفشل أمام الشيفرة.
  • تقول وثيقة تصميم إن verify-otp يضبط last_login_at ويخزّن الـ token على العميل (docs/plans/2026-07-06-shukah-auth-api-local.md:106-108). والـ handler لا يفعل أيًّا منهما.
  • تكتب وثيقة تصميم 2026-07-06 والمخطط التدفقي المسودة المسارات بالشكل /maeda/api/*. ومنذ commit d837492 (2026-07-10) صار كل route للعميل تحت PREFIX_API_V1/clients، ويجيب host المصادقة بـ 404 على /maeda/*.
  • 2026-10-02: قالت هذه الصفحة إن reset-otp الناجح لم يُلتقط. وفي 2026-10-02 أجابت كل من Shukah وLocate بـ 200 {"status":true} لهاتف معروف.
  • 2026-10-02: لم تعطِ هذه الصفحة body حاليًا لـ Shukah لرمز verify-otp خاطئ. وتجيب Shukah على dev بـ body من Laravel مع type من Symfony، يظهر تحت «الفروق عن Shukah».
  • 2026-10-02: قالت هذه الصفحة إن verify-otp الناجح لم يُلتقط مباشرة، وإن body النجاح في Shukah غير موثّق. وفي 2026-10-02 أجابت كل من Shukah وLocate بـ 200 {access_token, data} مع الملف الشخصي نفسه بـ 13 مفتاحًا وقيم متساوية، وظلت الـ tokens السابقة تعمل بعد تسجيل الدخول الجديد.

Sources​

  • locate-authentication-service@6757aa4: src/routes/client.js:11-12، src/controllers/client/reset-otp/handler.js:11-30,34-74، src/controllers/client/verify-otp/handler.js:14-53، src/controllers/client/__tests__/verify-otp.test.js:77-94, src/services/client-otp.js:5-45، src/shared/helpers/sms.js:4-24, src/shared/helpers/filter.js:1-4، src/sync/lib/casts.js:2-18,35-41, src/shared/errors/yii-bad-request-error.js:12-25، src/middlewares/error-handler.js:10-35, src/shared/constant/user.js:14,27-30، src/shared/helpers/jwt.js:4-10, src/services/merchant-token-bridge.js:6-16، src/services/shukah-sync.js:13-75,184-234, src/models/client.js:45-52,83-90,110-111,147-156، src/models/counter.js:7,17-24, src/services/__tests__/client-otp.test.js:6، k6/seed/seed-test-clients.js:23-24,40-58, k6/README.md:13-23
  • وثائق locate-authentication-service@6757aa4: docs/api/client-auth-and-address-book.md §1 و§2 (الأسطر 45-146، 542؛ آخر تغيير 2026-07-27)، docs/draft/api-auth-draft.md:5-31,90,109-116 (2026-07-07)، docs/draft/flow-chart-shukah2locate.md:60,140-142 (2026-07-08)، docs/plans/2026-07-06-shukah-auth-api-local.md:8-11,26,101-108,155,213,237-239، docs/plans/2026-07-16-shukah-token-outbound-push.md:10-28، docs/plans/2026-07-28-k6-load-test-suite.md:9,18-19,33-40
  • نسخة الجوال اللحظية mobile/locate-client@e960c3a (الفرع development، 2026-02-26؛ شيفرة التطبيق نفسه، وليست من Locate): src/entities/user/model/api/services/auth.service.ts:22-31,45-54,65-74، src/entities/user/model/api/dto/auth.dto.ts:43-88,103-122، src/shared/lib/helpers/phoneNumber/getClearPhoneNumber.ts:1-3، src/shared/lib/helpers/phone/getPlatform.ts:4-14، src/features/SignInForm/model/services/enterPhoneAuth.ts:48,53-67، src/shared/api/reqError.ts:125-127، src/features/RegisterNewUserForm/model/services/registerNewUser.ts:105، src/entities/user/model/services/sendCode/sendCode.ts:28-35، src/features/VerificationCodeForm/model/services/sendCode.ts:46-57
  • التقاطات dev، 2026-10-02 (parity audit): reset-otp--shukah وreset-otp--locate و reset-otp-missing--shukah وreset-otp-missing--locate وverify-otp-wrong--shukah و verify-otp-wrong--locate
  • التقاطات dev، 2026-10-01: probe-locate-reset-otp وprobe-locate-verify-otp و probe-shukah-reset-otp وprobe-auth-host-maeda-prefix