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

استبدال الـ token

الخلفية​

تُصدر Shukah token معتمًا (opaque). وتُصدر خدمة المصادقة JWT من Locate وترفض أي شيء آخر على routes الـ /api/v1/clients/* التي تتطلب تسجيل دخول: فtoken من Shukah يحصل على 401. ويمثّل استبدال الـ token الجسر بين النظامين. وهو موجود في خدمة المصادقة فقط. وليس لدى Shukah ما يقابله، فلا توجد واجهة من Shukah تُقابله.

وأثناء الترحيل قد يحمل التطبيق token لأحد النظامين ويحتاج إلى استدعاء الآخر. يحوّل هذا الـ endpoint الـ token الذي بحوزته إلى token يقبله النظام المستهدف.

الميزة​

أرسل الـ token الذي بحوزتك والنظام الذي توشك على استدعائه (target_platform: shukah أو locate). لا تحتاج إلى معرفة نوع الـ token الذي بحوزتك: فالخدمة تقرّر من شكله. ثلاثة أجزاء تفصلها نقاط تعني JWT من Locate، وأي شيء آخر هو token من Shukah.

الـ token الذي ترسلهtarget_platformما تحصل عليه
JWT من Locatelocateالـ JWT نفسه دون تغيير. ولا يُفحص.
token من Shukahshukahالـ token نفسه دون تغيير. ولا يُفحص.
JWT من Locateshukahtoken من Shukah مخزَّن لذلك العميل. ويُفحص الـ JWT أولًا.
token من ShukahlocateJWT جديد من Locate للعميل الذي يملك token Shukah.

الصف الأخير هو الوحيد الذي يكتب. فكل استدعاء ينشئ JWT جديدًا من Locate ويخزّنه ويدفعه إلى Shukah عندما يكون للعميل معرّف في Shukah ويكون الدفع الصادر (outbound push) مفعّلًا. أما tokens Locate التي يخزّنها فلا تُحذف أبدًا.

يوضح المخطط التدفقي المسودة لخدمة المصادقة (2026-07-08، انظر Sources تحت «التفاصيل») كيف ينبغي للتطبيق استخدامه. عند البدء يقرأ التطبيق إعداد العميل auth_source. فإذا كان لديه token محفوظ، يستدعي استبدال الـ token بذلك الـ token مع ضبط target_platform على auth_source. وإن لم يكن لديه، يسجّل الدخول برمز OTP على النظام المطابق. وهذا المخطط مسودة، ولا يزال يعرض المسار القديم /maeda/api/exchange-token.

لا يفعل التطبيق ذلك في نسخته اللحظية بتاريخ 2026-02-26. فلا توجد شيفرة في تطبيق الجوال تستدعي استبدال الـ token، وليس للتطبيق base URL لخدمة المصادقة ولا إعداد auth_source. لذا فالمستخدم الذي يحمل token من Shukah ويستدعي التحقق من الـ token على Locate يحصل على 401، ثم يحذف التطبيق الـ token المخزَّن، مما يسجّل خروج المستخدم. وهل يستدعي إصدار التطبيق الحالي، وهو أحدث من النسخة اللحظية، هذا الـ endpoint: غير موثّق بعد.

POST /api/v1/clients/exchange-token​

endpoint جديد — لا يوجد ما يقابله في Shukah.

الطلب (Request)​

لا حاجة إلى header باسم Authorization ولا يُقرأ: فالـ token المراد تحويله يوضع في JSON body.

الحقلالموضعالنوعمطلوبالقيودالوصف
tokenbodystringنعميجب ألا يكون فارغًا. ثلاثة أجزاء تفصلها نقاط تُعدّ JWT من Locate. ولا يُفحص أي شيء آخر عن الـ token في هذه الخطوة. والقيمة غير الفارغة التي ليست سلسلة تفشل بـ 401.الـ token الذي بحوزة التطبيق.
target_platformbodystringنعمتمامًا "shukah" أو "locate"، بأحرف صغيرة. وأي شيء آخر يعطي 400.النظام الذي يوشك التطبيق على استدعائه.
Content-Typeheaderstringنعمapplication/json. وبدونه لا يُقرأ الـ body، ويعطي الاستدعاء 400.—

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

200 مع الـ token الخاص بالنظام المستهدف:

الحقلالنوعيقبل nullالصيغة / القيودالوصف
statusintegerلادائمًا 200.—
data.tokenstringلاJWT من Locate عندما يكون target_platform هو locate. وtoken من Shukah عندما يكون shukah.الـ token الخاص بالنظام المستهدف.
{
"status": 200,
"data": {
"token": "<locate-jwt>"
}
}

التُقط على dev، 2026-10-01 (العميل 356259؛ الشكل فقط): token من Shukah أُرسل مع target_platform: "locate". وقيمة الـ token نائبة (placeholder)، لأن الـ tokens لا تُخزَّن أبدًا في هذا المستودع.

{
"status": 200,
"data": {
"token": "<shukah-token>"
}
}

التُقط على dev، 2026-10-01 (العميل 356259؛ الشكل فقط): ذلك الـ JWT من Locate أُعيد إرساله مع target_platform: "shukah". أما token من Shukah أُرسل مع target_platform: "shukah" فعاد دون تغيير.

قد يعود JWT من Locate أُرسل مع target_platform: "shukah" بالشكل {"status":200,"data":{}}، عندما لا تكون لـ token Shukah المخزَّن نسخة خام. وأي البيئات تتأثر بذلك: غير موثّق بعد (انظر «التفاصيل»).

الأخطاء​

HTTPBodyمتى
400{"status":400,"message":"Invalid token or target_platform"}token مفقود أو فارغ، أو target_platform ليس shukah ولا locate، أو ليس في الطلب Content-Type: application/json.
400{"status":400,"message":"Internal server error"}الـ body ليس JSON صالحًا. وخارج production تكون الرسالة نص المحلّل (parser)، مثل Unexpected end of JSON input، ويُضاف حقل stack.
401{"status":401,"message":"Unauthorization"}JWT من Locate إلى shukah: التوقيع غير صالح، أو الـ token مشوَّه، أو ليس للعميل token نشط من Shukah. token من Shukah إلى locate: لا يوجد token مخزَّن مطابق، أو العميل الذي يعود إليه غير موجود. وأي فشل غير متوقع داخل الاستدعاء يعطي 401 هذه أيضًا، مثل خطأ في قاعدة البيانات، أو token ليس سلسلة، أو token مكرر.

التُقط أول body بـ 400 على dev في 2026-10-01، مع JSON body فارغ ومرة أخرى مع target_platform بالقيمة nope. أما body الخاص بـ 401 فهو ما تُرجعه الشيفرة هنا؛ ولم تُثِره جولة الالتقاط على هذا الـ route. وأما 400 الخاص بـ JSON المشوَّه فمصدره اختبار دون اتصال لشيفرة الخدمة، لا dev.

جرّبه​

هذا الاستدعاء يكتب: ففي كل مرة يحوّل فيها token من Shukah إلى JWT من Locate يخزّن token جديدًا ويدفعه إلى Shukah. كما تكتب الخدمة الـ request body، متضمنًا الـ token، في log الخاص بها. لا ترسل عمليتي استبدال للعميل نفسه خلال ثانية واحدة.

export AUTH_BASE_URL=https://dev.authentication.locate.sa
export SHUKAH_TOKEN='<shukah-token>' # see "Getting a test token" on the module overview
curl -s -X POST "$AUTH_BASE_URL/api/v1/clients/exchange-token" -H "Content-Type: application/json" -d "{\"token\":\"$SHUKAH_TOKEN\",\"target_platform\":\"locate\"}"

المتوقع: 200 وJWT من Locate في data.token. احتفظ به في $TOKEN للصفحات الأخرى.

تنبيه​

  • الحالات التي يُعاد فيها الـ token دون تغيير لا تفحصه: فأي سلسلة من ثلاثة أجزاء تُرسل مع target_platform: "locate" تعيد 200. لاختبار token، استدعِ التحقق من الـ token.
  • token من Shukah يُرسل مع target_platform: "locate" يخزّن token جديدًا ويدفعه إلى Shukah في كل مرة. وقد يجعل استدعاءان للعميل نفسه في الثانية نفسها الثاني منهما يفشل بـ 401 (قراءة شيفرة، لم يُعاد إنتاجه).
  • في وضع production، وحدها الـ tokens التي دفعتها Shukah مباشرة لها نسخة خام مخزَّنة، والاستبدال يحتاج إليها. السلوك في production: غير موثّق بعد.

التفاصيل (للـ 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). لذا يُكتب token_raw على dev، لأنه لا يُخزَّن إلا خارج production (انظر «مجموعة token_bridge» أدناه).

الـ Route​

  • ليس للـ route أي middleware: لا حارس client، والـ handler غير ملفوف بـ asyncHandler (src/routes/client.js:22). وللـ handler try/catch خاص به، وكل استثناء بداخله يصبح 401 (src/controllers/client/exchange-token/index.js:10-96).
  • يقرأ express.json() الـ body (src/index.js:42). والطلب بدون Content-Type: application/json يترك req.body على {}، فينتج 400 المذكور أعلاه (الافتراضي في المكتبة، لم يُلتقط). ويسري حد الـ body الافتراضي في المكتبة وهو 100 kb.
  • يُكتب request body، متضمنًا الـ token، في log الخادم (src/controllers/client/exchange-token/index.js:32).

ما تفعله كل توليفة​

الـ token الداخلtarget_platformما يحدثالمصدر
JWT (ثلاثة أجزاء)locateيُعاد دون تغيير ودون تحقق. وسلسلة مزوَّرة من ثلاثة أجزاء تعيد 200 أيضًا.src/controllers/client/exchange-token/index.js:35-41
معتم (Opaque)shukahيُعاد دون تغيير ودون تحقق.src/controllers/client/exchange-token/index.js:35-41
JWTshukahيعمل verifyAccessToken، وأي استثناء يعطي 401. ثم يبحث عن صف token_bridge للعميل بقيمة source_system ضمن ['shukah','locate'] وis_active: true، دون ترتيب: فأي token نشط من Shukah مقبول (تقرّر في 2026-09-09). وعدم العثور على شيء يعطي 401. وإلا يُرجع الـ token الخام لذلك الصف. ولا يمكن أن تطابق 'locate' في القائمة أبدًا، لأن القيم الممكنة للـ enum هي shukah أو merchant.src/controllers/client/exchange-token/index.js:44-67؛ src/models/token-bridge.js:5-23؛ docs/plans/2026-07-08-token-bridge-generic-and-image-urls.md:200-224
معتم (Opaque)locateيجد الصف بحسب الـ token الخام، دون فلتر is_active. وعدم العثور على شيء يعطي 401. ثم يحمّل العميل دون فحص للحالة أو is_deleted. وعدم العثور عليه يعطي 401. وينشئ JWT بالقيمتين {client_id, phone}، ويُدرج صف token_bridge (source_system: 'merchant')، ويدفع الـ JWT إلى Shukah (بذل أقصى جهد)، ويجيب بـ 200.src/controllers/client/exchange-token/index.js:70-89
  • العميل غير النشط ما زال يحصل على JWT في الصف الأخير، لكن حارس client في الـ routes الأخرى يرفضه (src/middlewares/client.js:27-33، src/shared/constant/user.js:14).
  • إذا لم يكن للصف المطابق token خام، يُحذف المفتاح ويصبح الـ body هو {"status":200,"data":{}} (src/controllers/client/exchange-token/index.js:66؛ قراءة شيفرة).
  • لا يستطيع الـ endpoint التحقق من token: فهو لا يفحصه إلا عندما يضطر إلى البحث عن شيء.
  • استدعاءان من نوع معتم-إلى-locate للعميل نفسه في الثانية نفسها ينشئان JWTs متطابقين، لأن الـ token لا يحتوي جزءًا عشوائيًا وiat دقته ثانية واحدة. فيصطدم الإدراج الثاني بفهرس token_hash الفريد ويُرجع الـ catch-all قيمة 401. قراءة شيفرة وفحص حتمية (determinism) دون اتصال، لم يُعاد إنتاجه: هل يحدث على dev: غير موثّق بعد.

مجموعة token_bridge والدفع إلى Shukah​

  • يُخزَّن JWT من Locate في token_bridge بالقيمة source_system: 'merchant': client_id، وتجزئة SHA-256 للـ token بوصفها token_hash، وis_active: true، وcreated_at، وtoken_raw فقط عندما لا يكون NODE_ENV هو production (src/services/merchant-token-bridge.js:6-16، src/shared/helpers/jwt.js:12).
  • تأتي tokens Shukah (source_system: 'shukah') من ETL لجدول tokens في Shukah، مع token_raw فقط عندما لا يكون NODE_ENV هو production (src/sync/mappers/token-bridge-mapper.js:7-25)، ومن الدفع الوارد (inbound push) POST {PREFIX_API_V1}/admin/clients/tokens، الذي يخزّن دائمًا الـ token الخام (src/routes/admin.js:27,47، src/controllers/admin/create-token/handler.js:12-29، src/services/shukah-token-bridge.js:8-33).
  • يقرأ الاستبدال token_raw في الاتجاهين (src/controllers/client/exchange-token/index.js:66,72). ولا تحذف خدمة المصادقة الصفوف أو تعطّلها أبدًا فيما يخص tokens Locate: فكل استدعاء معتم-إلى-locate يضيف صفًا واحدًا. ولا شيء يُبطل JWT من Locate.
  • الدفع هو POST {SHUKAH_HOST}/maeda/client/add-token مع {access_token, client_id: <shukah_id>} والـ headers x-from-application: LOCATE_CLIENT وAuthorization: Bearer <SHUKAH_ADMIN_TOKEN>. وهو بذل أقصى جهد ويُنتظر إتمامه داخل الاستدعاء، بلا مهلة. ولا يفعل شيئًا عندما يكون shukah_id قيمة null، أو المزامنة معطّلة، أو الـ host أو token المسؤول غير مضبوط (src/services/shukah-sync.js:13-75، src/config/envConfig.js:19-33). وبهذه الطريقة تتعرف Shukah على JWTs من Locate (docs/plans/2026-07-16-shukah-token-outbound-push.md:10-28).

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

  • تسرد وثيقة الخدمة 401 لحالة "JWT expired" (docs/api/client-auth-and-address-book.md:404). وJWTs من Locate لا انتهاء لها (src/shared/helpers/jwt.js:4-6)، لذا لا يمكن أن تحدث هذه الحالة.
  • يكتب المخطط التدفقي المسودة ووثيقة تصميم 2026-07-06 هذا الـ route بالشكل POST /maeda/api/exchange-token. وفي commit c2d1c9f (2026-07-08) كانت الخدمة تركّبه هناك فعلًا، دون البادئة. ثم نقل commit d837492 (2026-07-10) كل routes العميل تحت PREFIX_API_V1/clients، ويجيب host المصادقة الآن بـ 404 على /maeda/*.
  • تقول وثيقتان إن الـ token الخام "never populated in prod" (docs/guides/shukah-migration-schema-reference.md:113، docs/plans/2026-07-08-token-bridge-generic-and-image-urls.md:106-108). لكن كاتب الدفع الوارد يخزّنه دون شرط (src/services/shukah-token-bridge.js:16). وحدهما ETL وكاتب JWT من Locate مقيَّدان بغير production (src/sync/mappers/token-bridge-mapper.js:22، src/services/merchant-token-bridge.js:14).

غير موثّق بعد​

  • في وضع production، أي tokens Shukah المخزَّنة تحمل نسخة خام، وما الذي يُرجعه استدعاء JWT-إلى-shukah عند غيابها. وفق الشيفرة هو {"status":200,"data":{}}.
  • هل الدفع الصادر إلى Shukah مفعّل على dev (SHUKAH_SYNC_ENABLED وSHUKAH_HOST و SHUKAH_ADMIN_TOKEN).
  • هل يفشل فعلًا استبدالان للعميل نفسه في الثانية نفسها.
  • هل يستدعي إصدار التطبيق الحالي (الأحدث من النسخة اللحظية 2026-02-26) هذا الـ endpoint.

Sources​

  • locate-authentication-service@6757aa4: src/routes/client.js:22، src/controllers/client/exchange-token/index.js:10-96, src/index.js:42، src/shared/helpers/jwt.js:4-12, src/services/merchant-token-bridge.js:6-16، src/services/shukah-token-bridge.js:8-33, src/sync/mappers/token-bridge-mapper.js:7-25، src/models/token-bridge.js:5-23, src/routes/admin.js:27,47، src/controllers/admin/create-token/handler.js:12-29، src/services/shukah-sync.js:13-75, src/config/envConfig.js:19-33، src/middlewares/client.js:27-33, src/shared/constant/user.js:14، src/middlewares/error-handler.js:12-35, src/shared/constant/error-message.js:59
  • وثائق locate-authentication-service@6757aa4: docs/api/client-auth-and-address-book.md §9 (الأسطر 372-408؛ آخر تغيير 2026-07-27)، docs/draft/flow-chart-shukah2locate.md:15-36,54,69-112 (2026-07-08)، docs/plans/2026-07-06-shukah-auth-api-local.md:8-11,26، docs/plans/2026-07-08-token-bridge-generic-and-image-urls.md:106-108,196-224 (2026-09-09)، docs/plans/2026-07-16-shukah-token-outbound-push.md:10-28، docs/guides/shukah-migration-schema-reference.md:113
  • نسخة الجوال اللحظية mobile/locate-client@e960c3a (الفرع development، 2026-02-26؛ شيفرة التطبيق نفسه، وليست من Locate): البحث في الشجرة كاملة عن exchange-token وapi/v1/clients وauthentication.locate وauth_source لا يجد شيئًا؛ src/entities/user/model/api/services/auth.service.ts:85-112 (يُحذف الـ token المخزَّن عند 401 في :101-103)؛ src/entities/settings/model/api/dto/settings.dto.ts:112-126 (نوع client-settings لا يحتوي حقل auth_source)
  • التقاطات dev، 2026-10-01: probe-locate-exchange-token وprobe-auth-host-maeda-prefix، واستدعاءات exchange-token المسجَّلة في ملخص الالتقاط المباشر (الشكل فقط، ولم تُخزَّن الـ tokens)