التحقق من الـ token
الخلفية
على Shukah كان التطبيق يستدعي GET /maeda/api/verify-token للتحقق من الـ token المحفوظ وتحميل
الملف الشخصي. تُرجع خدمة المصادقة (auth service) الملف الشخصي نفسه بأسماء حقول Shukah نفسها، لذا فالتحوّل
في معظمه تغيير في base URL وبادئة المسار. وثلاثة أمور أخرى تستحق العناية: أنه يقبل
JWT من Locate فقط، وأن body الخاص بـ 401 مختلف، وأنه لا يرسل headers إصدار التطبيق (app-version headers) التي
يقرؤها التطبيق من هذا الاستدعاء.
الميزة
أرسل الـ JWT من Locate الصادر عن exchange-token واحصل على الملف الشخصي
للعميل. الاستدعاء للقراءة فقط: فلا يجدّد الـ token ولا يغيّر أي شيء. ويجيب بـ 401 عندما
يكون الـ token مفقودًا أو غير صالح، أو عندما لا يكون حساب العميل نشطًا.
في نسخة التطبيق اللحظية بتاريخ 2026-02-26، يستدعيه التطبيق في كل مرة يبدأ فيها، للتحقق من الـ token المحفوظ
وتحميل الملف الشخصي، ومرة أخرى من الشاشة الرئيسية (Home). وعندما تكون الإجابة 401، يحذف التطبيق
الـ token المخزَّن.
GET /api/v1/clients/verify-token
GET /maeda/api/verify-token✅ تم الاستبدال- 🔴 يحصل token من Shukah على 401 "Unauthorization"؛ أرسل JWT من Locate (من exchange-token؛ واحتفظ بـ token من Shukah).
- 🔴 عند هذا الـ 401 يحذف التطبيق الـ token المخزَّن (auth.service.ts:101)، لذا استبدل الـ token أولًا.
- 🟡 body الـ 401 في Locate هو {status,message} وليس body Laravel؛ ويقرأ التطبيق message فقط.
- 🟡 لا ترسل Locate رؤوس إصدار التطبيق (new_version وغيرها)؛ ورؤوس Shukah غير معروفة (لم تُسجَّل رؤوس الاستجابة).
الطلب (Request)
| الحقل | الموضع | النوع | مطلوب | القيود | الوصف |
|---|---|---|---|---|---|
Authorization | header | string | نعم | Bearer <locate-jwt>. ويُرفض token Shukah بـ 401. | الـ JWT من Locate. ويحصل عليه التطبيق اليوم عبر exchange-token ويحتفظ بـ token Shukah؛ ويُرجع verify-otp في Locate واحدًا أيضًا، لكن تسجيل الدخول يبقى على Shukah حاليًا. |
language | query | string | لا | يُقبل ويُتجاهل. | يُبقى حتى تظل سلسلة query القديمة لـ Shukah تعمل. |
| أي مفتاح query أو header آخر | query، header | — | لا | يُقبل ويُتجاهل، مثل x-from-application. | — |
الاستجابة (Response)
200 مع الملف الشخصي ككائن مجرد (بلا غلاف status أو data):
| الحقل | النوع | يقبل null | الصيغة / القيود | الوصف |
|---|---|---|---|---|
client_id | integer | لا | الرقم نفسه كما في Shukah للعميل المرحَّل (356259 لعميل الاختبار على dev). والعملاء المسجَّلون في Locate يحصلون على معرّفات من 1,000,000,001 فصاعدًا. | معرّف العميل. وهو داخل الـ JWT أيضًا. |
odoo_id | string | نعم | سلسلة رقمية لعميل الاختبار؛ وnull عندما لا يكون للعميل واحد. | معرّف العميل في Odoo (نظام ERP). |
contact_phone | string | نعم | الهاتف تمامًا كما خُزِّن؛ ولا تعيد الخدمة تنسيقه. فالهواتف المسجَّلة من التطبيق تُخزَّن كما أرسلها التطبيق، والهواتف المرحَّلة تحتفظ بتنسيق Shukah (تُحذف المسافات في الحالتين). | هاتف العميل. |
status | string | لا | active أو pending أو suspended أو blocked أو expired. ويُرجع هذا الـ route القيمة active فقط، لأن كل حالة أخرى تحصل على 401. | حالة الحساب. |
email_address | string | نعم | لا فحص للصيغة. قد يكون "" بعد تحديث الملف الشخصي بقيمة "". | بريد العميل الإلكتروني. |
first_name | string | نعم | تُحذف المسافات الزائدة، وبحد أقصى 100 حرف عند الكتابة. قد يكون "" بعد تحديث الملف الشخصي بقيمة "". | الاسم الأول. |
last_name | string | نعم | تُحذف المسافات الزائدة، وبحد أقصى 100 حرف عند الكتابة. قد يكون "" بعد تحديث الملف الشخصي بقيمة "". | اسم العائلة. |
sex | integer | لا | 0 غير معروف أو غير مُدخَل، 1 ذكر، 2 أنثى. | الجنس. |
birth_date | string | نعم | YYYY-MM-DD: تاريخ UTC للقيمة التي كُتبت. | تاريخ الميلاد. |
receive_offers | integer | لا | 0 أو 1. | هل يتلقى العميل العروض. |
push_enabled | integer | لا | 0 أو 1. | هل إشعارات push مفعّلة. |
is_food_truck_user | integer | لا | 0 أو 1. | علامة (flag) مستخدم عربة الطعام (food-truck). |
is_cod_disabled | integer | لا | 0 أو 1. | 1 عندما يكون الدفع عند الاستلام معطّلًا للعميل. |
{
"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
}
التُقط على dev، 2026-10-01 (العميل 356259)؛ واستُبدلت الحقول الشخصية.
الأخطاء
| HTTP | Body | متى |
|---|---|---|
401 | {"status":401,"message":"Unauthorization"} | لا يوجد header باسم Authorization، أو لا يحتوي على جزء الـ token. |
401 | الـ body نفسه. | الـ token ليس JWT من Locate: token من Shukah، أو token مشوَّه، أو ذو توقيع خاطئ. |
401 | الـ body نفسه. | الـ token صالح لكن العميل المرتبط به غير موجود، أو حالة العميل ليست active (معلّق أو موقوف أو محظور أو منتهٍ). |
500 | {"status":500,"message":"Internal server error"} | فشل غير متوقع داخل الخدمة. لم يُلتقط. وخارج production تكون الرسالة نص الخطأ الخام ويُضاف حقل stack. |
التُقط body الخاص بـ 401 على dev في 2026-10-01 دون token، ومرة أخرى مع token من Shukah.
جرّبه
export AUTH_BASE_URL=https://dev.authentication.locate.sa
export TOKEN='<locate-jwt>' # see "Getting a test token" on the module overview
curl -s "$AUTH_BASE_URL/api/v1/clients/verify-token" -H "Authorization: Bearer $TOKEN"
المتوقع: 200 والملف الشخصي المبيّن تحت «الاستجابة». ومع token من Shukah في $TOKEN تحصل على
401 المبيّن تحت «الأخطاء».
الفروق عن Shukah
body الخاص بـ 200 هو نفسه: ففي 2026-10-01 أرجعت الخدمتان المفاتيح الـ 13 والأنواع والقيم نفسها
للعميل 356259. وتغطي الصفوف أدناه الـ token وURL وbody الخطأ وheaders إصدار التطبيق.
| الجانب | Shukah | Locate | الأثر على التطبيق |
|---|---|---|---|
| الـ Token | token معتم (opaque) من Shukah. | JWT من Locate فقط. وtoken من Shukah يحصل على 401 (التُقط على dev، 2026-10-01). | استبدل token Shukah بالـ JWT قبل التحوّل، واحتفظ بـ token Shukah لـ order-v2. |
| URL | GET $SHUKAH_BASE_URL/maeda/api/verify-token | GET $AUTH_BASE_URL/api/v1/clients/verify-token. لا يخدم host المصادقة /maeda/*: بل يجيب بـ 404. | غيّر base URL والمسار. |
body الخاص بـ 401 | {"name":"Unauthorized","message":"Your request was made with invalid credentials.","code":0,"status":401,"type":"Illuminate\\Auth\\AuthenticationException"} | {"status":401,"message":"Unauthorization"} | يجب تغيير أي شيء يطابق نص الخطأ أو name أو type. وقيمة status هي 401 في الاثنين. |
| headers إصدار التطبيق | يقرأ التطبيق new_version وold_version وhas_new_version وis_required_update من headers الاستجابة لأي URL يحتوي على api/verify-token أو client/points (قائمته الافتراضية)، ويستخدمها في نافذة التحديث المنبثقة. وهل ترسلها Shukah: غير موثّق بعد، لأن الالتقاطات تضم headers الطلب فقط. | لا تُرسل: فلا خدمة من Locate تضبط هذه الـ headers. ولا يحتوي URL الخاص بـ Locate وهو /api/v1/clients/verify-token على api/verify-token، فلن يبحث التطبيق عنها هناك. | إذا كانت Shukah ترسل الـ headers اليوم، فستتوقف نافذة التحديث عن الظهور بصمت بعد التحوّل. |
تنبيه
- لا يعمل هنا سوى JWT من Locate. فtoken من Shukah يحصل على
401ويحذف التطبيق الـ token المخزَّن عند401، لذا استبدله بالـ JWT أولًا، واحتفظ بـ token Shukah: فما زال order-v2 يحتاجه. - token من Locate لا تنتهي صلاحيته ولا يُبطَل أبدًا، لذا فإن
401تعني token سيئًا أو حسابًا غير نشط، لا انتهاء مهلة أبدًا. - body الخاص بـ
401هو{"status":401,"message":"Unauthorization"}الخاص بـ Locate (بهذا الإملاء)، لا الخاص بـ Shukah.
التفاصيل (للـ 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).
الـ Route والحارس (guard)
يشغّل GET /clients/verify-token الـ middleware المسمّى client ثم handler من سطر واحد يُرجع
العميل الذي حمّله الـ middleware (src/routes/client.js:13،
src/controllers/client/verify-token/index.js:7).
- يقرأ الحارس الجزء الثاني فقط من
Authorization(بعد المسافة). والكلمة الأولى (BearerأوTokenأو أي شيء) لا تُفحص. والـ header الذي لا جزء ثانيَ فيه يفشل بالرسالة "jwt must be provided" ويعطي401(src/middlewares/client.js:15-22,38-41). - الـ token المشوَّه أو ذو التوقيع الخاطئ أو token من Shukah يجعل
jwt.verifyيُلقي استثناءً، ويجيب الحارس بـ401(src/middlewares/client.js:22,38-41). - يحمّل الحارس العميل بحسب
client_idالموجود في الـ token. والعميل المفقود يعطي401(src/middlewares/client.js:27-33). - لا يُفحص: علم الحذف الناعم (soft-delete)، ولا
phoneالموجود في الـ token مقابل الهاتف المخزَّن، ولا صفوفtoken_bridge، ولا الجهاز ولا IP (src/services/client.js:5-10: الاستعلام هو{ client_id }فقط).
قواعد الـ Token
يُوقَّع JWT من Locate بـ HS256 ويحمل {client_id, phone, iat}. وتوقّع الخدمة وتتحقق
دون خيارات، لذا لا يحمل أي token قيمة exp: فلا شيء ينتهي ولا شيء يُبطَل
(src/shared/helpers/jwt.js:4-10).
قاعدة الحالة النشطة
وحده ACTIVE يستطيع المصادقة. قيمة CLIENT_ACTIVE_STATUSES هي [ACTIVE]، لذا فإن PENDING وSUSPENDED
وBLOCKED وEXPIRED وغياب الحالة كلها تعطي 401
(src/shared/constant/user.js:4-14، src/middlewares/client.js:27-33).
يُخزَّن البحث عن العميل مؤقتًا (cache) لمدة CLIENT_CACHE_TTL ثانية، والافتراضي 300
(src/services/client.js:5-10). والكتابة عبر save() في Mongoose تمسح الـ cache. أما الكتابة
الجماعية في ETL (bulkWrite) فلا تفعل، لذا قد يستغرق تغيير الحالة القادم من مزامنة Shukah حتى هذه المدة
ليُطبَّق (src/models/client.js:158-161).
محوّل الملف الشخصي (mapper)
تبني formatClientProfileForResponse الـ body (src/controllers/client/setting/mapper.js:24-38).
statusهو الصيغة بأحرف صغيرة للحالة المخزَّنة. والقيمة المخزَّنة من خارج الخمس تصبحpending(src/controllers/client/setting/mapper.js:5-11,28). وتحوّل المزامنة نص حالة Shukah إلى الحالة المخزَّنة فيsrc/sync/lib/casts.js:24-33.sexيأتي من الجنس المخزَّن:MALEهي1وFEMALEهي2وأي شيء آخر0(src/controllers/client/setting/mapper.js:14-18). والأعلام الأربعة قيمتها0أو1(src/controllers/client/setting/mapper.js:20).contact_phoneهو الهاتف المخزَّن. والهاتف المسجَّل عبر reset-otp يُخزَّن كما أُرسل، مع حذف المسافات (src/controllers/client/reset-otp/handler.js:40,56،src/sync/lib/casts.js:40-41). وتمر الهواتف المرحَّلة عبر المطبِّع (normaliser) نفسه الذي يحذف المسافات (src/sync/mappers/client-mapper.js:99).- قواعد الكتابة وراء قيود الحقول:
- يُكتب
first_nameوlast_nameبعد حذف المسافات الزائدة. ويقبل تحديث الملف الشخصيnullأو سلسلة بحد أقصى 100 حرف بعد حذف المسافات، ويجيب بـ400خلاف ذلك. وتُخزَّن""كما هي"". ويحدّ النموذج الاسمين أيضًا بـ 100 حرف، لذا يفشل التسجيل باسم أطول (src/controllers/client/update-profile/validate.js:4-6,24-25،src/controllers/client/update-profile/index.js:13-15،src/controllers/client/update-profile/parse-payload.js:5,11-12،src/models/client.js:45-52). email_address: يقبل تحديث الملف الشخصي أي سلسلة أوnull، دون فحص للصيغة ودون حد للطول، ويخزّنها بعد حذف المسافات الزائدة، فتبقى""كما هي""(src/controllers/client/update-profile/validate.js:26،src/controllers/client/update-profile/parse-payload.js:13،src/models/client.js:57-60).- يُحفظ
birth_dateكسلسلةYYYY-MM-DD، وهي تاريخ UTC للقيمة المكتوبة. ويجيب تحديث الملف الشخصي بـ400عن قيمة لا يستطيعnew Date()قراءتها، ويمسحهاnull(src/sync/lib/casts.js:2-11،src/controllers/client/update-profile/validate.js:27-30،src/controllers/client/update-profile/parse-payload.js:14-16). وتعطي وثيقة الخدمة الصيغةYYYY-MM-DD، وnullعند الفراغ (docs/api/client-auth-and-address-book.md:454).
- يُكتب
- يُرجع الكائن نفسه كل من verify-otp (
data) وPUT /api/v1/clients/settingوPUT /api/v1/clients/:client_id. أماGET /api/v1/usersفيُرجع كائنًا مختلفًا في أحد الأوضاع (انظر تفاصيل المستخدم). - المعرّفات: تنسخ المزامنة معرّف Shukah للعميل المرحَّل إلى
client_id(src/sync/mappers/client-mapper.js:90). والعملاء المسجَّلون في Locate يحصلون على1,000,000,000 + sequence(src/models/counter.js:7,23).
حيث تختلف الوثائق عن الشيفرة (القول للشيفرة)
- تقول وثيقة الخدمة إن
statusهو"active"أو"inactive"(docs/api/client-auth-and-address-book.md:177). وتُرجع الشيفرة القيم الخمس أعلاه، وتسردها الوثيقة نفسها في السطر 453.
Sources
locate-authentication-service@6757aa4:src/routes/client.js:13،src/middlewares/client.js:8-42,src/controllers/client/verify-token/index.js:3-7،src/controllers/client/setting/mapper.js:5-38,src/shared/helpers/jwt.js:4-10،src/shared/constant/user.js:4-14,src/services/client.js:5-10,src/models/client.js:158-161،src/models/counter.js:7,23,src/sync/lib/casts.js:2-11,24-33,40-41،src/sync/mappers/client-mapper.js:90,99,src/shared/constant/error-message.js:59،src/middlewares/error-handler.js:12-35,src/models/client.js:45-52,57-60،src/controllers/client/update-profile/validate.js:4-6,24-30،src/controllers/client/update-profile/index.js:13-15،src/controllers/client/update-profile/parse-payload.js:5,11-16،src/controllers/client/reset-otp/handler.js:40,56- وثائق
locate-authentication-service@6757aa4:docs/api/client-auth-and-address-book.md§3 (الأسطر 150-183، 453، 454؛ آخر تغيير 2026-07-27)،docs/plans/2026-07-14-shukah-verify-token-api-local.md§2 (2026-07-14) - نسخة الجوال اللحظية
mobile/locate-client@e960c3a(الفرعdevelopment، 2026-02-26؛ شيفرة التطبيق نفسه، وليست من Locate):platforms/mobile/products/locate/app/sideEffects/effects/initSecondaryResources.tsx:37-43،platforms/mobile/products/locate/screens/home/mainInner/Home/components/Screen/Home.tsx:53،src/entities/user/model/services/init/init.ts:21،src/entities/user/model/services/fetchUserInfo/fetchUserInfo.ts:50،src/entities/user/model/api/services/auth.service.ts:85-112(يُحذف الـ token المخزَّن عند401في:101-103)،src/shared/api/interceptors/createResponseInterceptor.ts:18-29،src/shared/features/featuresConfigs.ts:41,90,107-110،src/features/AppVersionUpdate/ui/AppVersionUpdatePopup/AppVersionUpdatePopup.tsx:64 - البحث في
locate-authentication-service@6757aa4وlocate-point-service@2c44ce3وlocate-order-v2-api@d9e4e5f6عنnew_versionوold_versionوhas_new_versionوis_required_updateلا يجد أي تطابق (2026-10-01). - التقاطات dev، 2026-10-01:
locate-verify-tokenوshukah-verify-tokenوlocate-verify-token-with-shukah-tokenوprobe-locate-verify-tokenوprobe-shukah-verify-tokenوprobe-auth-host-maeda-prefix