Skip to main content

User detail

Background​

GET /api/v1/users is the auth service's general "whose token is this?" route. It was written before the migration, and no Shukah API maps to it. With x-auth-from: SHUKAH it checks a token and returns the client's profile.

It matters for the migration because other backends use it. order-v2 and the point service call it to turn the token they received into a client. That token can be a Shukah token or a Locate JWT, so this route decides which tokens the order-v2 point routes accept.

Feature​

Send the token in Authorization and say which backend verifies it in x-auth-from. The callers send SHUKAH.

  • order-v2: its profile guard, which protects many client routes such as orders and gift cards, calls this route with x-auth-from: SHUKAH.
  • The point service: its client middleware calls this route to find out who the caller is. It sends x-auth-from from its own AUTH_VERIFY_APPLICATION setting (SHUKAH by default), reads the client id from the answer, and gives its own caller 401 when this route does not answer with a 2xx.
  • Other callers: Not documented yet.

The app itself does not call this route: the app snapshot of 2026-02-26 has no call to it. The app does not need to send x-auth-from to the order-v2 point routes, because order-v2 and the point service set it themselves when they call this route.

Which tokens work depends on the auth_source client setting (shukah or locate), which locate-go owns. If the service cannot read the setting, it uses locate.

Tokenauth_source is not locateauth_source is locate
Shukah tokenWorks. The service asks Shukah to check it (GET /maeda/api/verify-token) and returns Shukah's answer unchanged.Works when the token is one of the service's active stored tokens. Not captured.
Locate JWTWorks only if Shukah knows the JWT, because the service still asks Shukah.Never works at the pinned code: it always gets 401. This is a defect.

On dev, on 2026-10-01, a Shukah token and a Locate JWT both got 200 with the same body. On 2026-10-02 that changed. Dev's auth_source was shukah (read from the public client settings), so this route passes the token on to dev Shukah. The order-v2 point routes, which call this route, gave a Locate JWT 401 and a Shukah token 200: dev Shukah no longer accepted the JWT (an inference; this route itself was not called with x-auth-from that day). With auth_source set to locate the JWT would fail too, because of the defect in the table. So on dev today only a Shukah token works here, and no setting alone makes the Locate JWT work.

GET /api/v1/users​

New endpoint — no Shukah equivalent.

Request​

FieldInTypeRequiredConstraintsDescription
Authorizationheaderstringyes, except for MERCHANTBearer <token>. The second space-separated part is the token. Missing gives 401.The token to check. On dev today only a Shukah token passes (2026-10-02).
x-auth-fromheaderstringyesOne of SHUKAH, LOCATE_V2, ADMIN_SHUKAH, ADMIN_LOCATE or MERCHANT. It is case-sensitive: with Authorization present, shukah gives 422. The callers use SHUKAH, and the rest of this page is about it.Which backend verifies the token.
access-token, client, uidheaderstringMERCHANT onlyAll three are required for MERCHANT.A merchant session.

Response​

For x-auth-from: SHUKAH, 200 with the client's profile as a bare object. The body depends on how the service checked the token:

How the token was checkedBody
Relay: auth_source is not locate.Shukah's own verify-token body, unchanged. On dev it had the same 13 fields as Verify token: status is active, sex is 0, 1 or 2, and the flags are 0 or 1.
Local, Shukah token: auth_source is locate.A different shape: status is the raw ACTIVE, sex is MALE, FEMALE or UNKNOWN, the four flags are booleans, and odoo_id is left out when the client has none. Not captured.
Local, Locate JWT: auth_source is locate.No success: 401 every time at the pinned code.
{
"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
}

Captured on dev, 2026-10-01 (client 356259), once with a Shukah token and once with a Locate JWT: both calls returned this body. Personal fields replaced. On 2026-10-02 a Locate JWT got 401 from the point routes that call this route, so this body is what a Shukah token gets today.

Errors​

HTTPBodyWhen
401{"status":401,"message":"Unauthorization"}There is no Authorization header (except for MERCHANT). Shukah rejected the token, or the token is not among the stored tokens. Any exception while the token is checked. auth_source is locate and the token is a Locate JWT.
422{"status":422,"message":"Your application is not support"}Authorization is present, but x-auth-from is missing or is not one of the five values.
404{"status":404,"message":"User not found"}x-auth-from: LOCATE_V2 only, when the legacy user is missing.
500{"status":500,"message":"Internal server errors"}The handler throws, or the MERCHANT upstream fails with anything but 401.

The 401 body was captured on dev on 2026-10-01 with no token. The 422 Your application is not support body was captured on dev on 2026-10-02, with a Locate JWT and no x-auth-from. The other bodies are from the code.

Try it​

export AUTH_BASE_URL=https://dev.authentication.locate.sa
export SHUKAH_TOKEN='<shukah-token>' # a Locate JWT is expected to get 401 here on dev today (inference); see Feature
curl -s "$AUTH_BASE_URL/api/v1/users" -H "Authorization: Bearer $SHUKAH_TOKEN" -H "x-auth-from: SHUKAH"

Expected on dev (2026-10-02, inference: this route was not called with x-auth-from that day, the point routes that call it were): 200 and the profile shown under Response for a Shukah token, and 401 for a Locate JWT, because dev passes it on to Shukah, which no longer accepts it. On 2026-10-01 both tokens got 200. Without x-auth-from you get the 422 shown under Errors.

Watch out​

  • A Locate JWT is expected to get 401 here on dev today (inference from the point routes, 2026-10-02), and at the pinned code it always would with auth_source set to locate, so do not promise Locate JWT support on this route.
  • x-auth-from is case-sensitive: SHUKAH works, shukah gets 422. One service doc writes it in lower case.
  • In relay mode each call also goes to Shukah and updates a legacy user record, and a Locate JWT works only if Shukah knows it.

Details (for backend)​

Only dev was checked: host https://dev.authentication.locate.sa, prefix /api/v1. The UAT and production hosts and prefix: Not documented yet.

Callers​

  • The point service: clientAuthMiddleware needs Authorization: Bearer <token>, then calls GET {AUTH_SERVICE_URL}/api/v1/users with that header, x-auth-from set to AUTH_VERIFY_APPLICATION (default SHUKAH) and Accept: application/json. A non-2xx answer becomes 401. The client id is the first non-null of client_id, user_id and id in the answer, and it must be a positive integer. The caller's own x-auth-from is never forwarded.
  • order-v2: UserProfileGuard makes the same call with the Authorization header it received and x-auth-from: SHUKAH. It is applied with @UseGuards(UserProfileGuard) on many client controllers, for example orders and gift cards.

The five x-auth-from values​

Order of checks: MERCHANT first, then a missing Authorization (401), then the value of x-auth-from (422), then a switch. The comparison is case-sensitive (src/middlewares/user.js:13-44, src/shared/constant/auth.js:1-7).

x-auth-fromWhat happensSuccess bodySource
SHUKAHThe token is checked as in the next table.The profile.src/middlewares/user.js:36-37,100-187
LOCATE_V2 (the switch's default case)The token is looked up in the legacy users collection. A legacy DEACTIVATE user gives 401. The handler then returns the stored user document, or 404.The legacy user document.src/middlewares/user.js:42-43,201-228; src/controllers/users/detail/handler.js:26-36
ADMIN_SHUKAHThe token must equal the configured admin token, else 401.An empty 200.src/middlewares/user.js:38-39,189-199; src/controllers/users/detail/handler.js:15-17
ADMIN_LOCATEGET {LOCATE_GO_BASE_URL}/api/admin/api/v1/profile with the Bearer token. Any failure gives 401.The upstream profile.src/middlewares/user.js:40-41,250-275
MERCHANTNeeds the headers access-token, client and uid. A missing one gives 422 {"status":422,"message":"Unauthorization"}. Then GET {MERCHANT_BASE_URL}/api/v1/users/profile. An upstream 401 gives 401, other failures give 500.The upstream profile.src/middlewares/user.js:54-98
Missing or any other value, with Authorization present422 {"status":422,"message":"Your application is not support"}.—src/middlewares/user.js:28-33; src/shared/constant/error-message.js:62

How SHUKAH checks a token​

auth_sourceLocate JWT (exactly three dot-separated parts)Opaque tokenSource
locateverifyClientToken calls ClientService.findByToken, which throws TypeError: Client.findByToken(...).cacheQuery is not a function. The model's static is async, so it returns a Promise, and a Promise has no cacheQuery. The catch turns it into 401, every time. Offline check with a valid JWT and the real middleware.Finds an active token_bridge row by raw token, then the client. Not found gives 401. Found sets the profile from toShukahClientProfile (raw types).src/middlewares/user.js:104-130,230-248; src/services/client.js:12-35; src/models/client.js:163-175
Any other value (normally shukah)Relayed: GET {SHUKAH_HOST}/maeda/api/verify-token with Authorization: Bearer <token>. A 200 with a body becomes the profile, as Shukah sent it. Any failure gives 401.The same relay.src/middlewares/user.js:132-186

getAuthSource() resolves the value in this order: the AUTH_SOURCE environment variable, then the cached auth_source of the settings:client key in Redis, then GET {LOCATE_API_URL}/settings/client, then the default locate (src/services/setting.js:12-51, docs/plans/2026-08-25-point-ledger-shared-write-contract.md:408-421). A Kafka message from locate-go refreshes the cache (src/events/settingConsumer.js:14-33, docs/plans/2026-07-28-settings-broker-consumer.md:43-68).

  • Neither locate branch checks the account status. ClientService.findOne filters only on client_id, and Client.findByToken filters only on is_deleted: false and the token's phone. So this route does not enforce active-only the way the client middleware does (code reading, src/services/client.js:5-17, src/models/client.js:163-175).
  • In the relay branch every call writes to the legacy users collection: an upsert from Shukah's payload. status active becomes ACTIVE, anything else DEACTIVATE, and sex 0 becomes MALE, anything else FEMALE. That sex rule differs from the profile's own coding (1 male, 2 female) (src/middlewares/user.js:145-172).
  • The relay accepts a Locate JWT only if Shukah knows it. That depends on the push of new Locate JWTs to Shukah (see Exchange token), so a client whose push failed or was skipped (no Shukah id) would get 401 here while auth_source is shukah (code reading).
  • Timing on dev: 630 ms for the Shukah token and 1061 ms for the Locate JWT, against 161 to 331 ms for calls that stay inside the service. That fits an upstream hop.

What the two captures do not explain​

Both captured calls returned the Shukah-format body (lower-case status, integer sex, 0 and 1 flags) and were slow. At the pinned code only the relay branch can produce that body for a Locate JWT. So either dev's auth_source was not locate on 2026-10-01, and Shukah accepted the Locate JWT (that would fit the exchange-token call that minted it having pushed it to Shukah, if the push is on for dev), or dev is not running 6757aa4. Neither the code nor the docs say which: Not documented yet. This is an inference, not a fact.

To tell them apart, compare the process uptime from the unauthenticated /health route (no prefix) with the 2026-10-01 11:57 +0700 merge of 6757aa4, and read auth_source from the client settings (src/routes/health.js:8, src/controllers/health/handler.js:11-19).

Answered on 2026-10-02. The public client settings (GET https://dev.go.locate.sa/api/v1/settings/client, the host in the app's SETTINGS_BASE_URL) returned "auth_source":"shukah". An AUTH_SOURCE environment variable would override it and cannot be read from outside, so this is very likely, not proven. The /health uptime of 90,815 s means the service started about 2026-10-01 06:43 UTC, 1 h 46 min after the 6757aa4 merge (04:57 UTC), so dev most likely runs 6757aa4 or later. So the first explanation is the probable one: on 2026-10-01 the relay branch ran and dev Shukah accepted the Locate JWT. On 2026-10-02 the same relay refused it, which is why the order-v2 point routes give a Locate JWT 401 Invalid or expired token (locate-point-service: services/api/src/services/client-auth.service.ts:17,22-29). Why dev Shukah stopped accepting the JWT: Not documented yet. The likely cause is that the Locate-to-Shukah token push (src/config/envConfig.js:22-35) did not reach, or no longer lives on, the Laravel port of dev Shukah (inference).

The locate + JWT defect is also in origin/production at 4f3bc96 (2026-09-21), which has the same src/services/client.js and src/models/client.js as 6757aa4. Whether anything runs from that ref, and whether a fix is planned before auth_source flips: Not documented yet.

Where the docs and the code disagree (code wins)​

  • A design doc writes the header value as x-auth-from: shukah (docs/plans/2026-08-25-point-ledger-shared-write-contract.md:418). The code compares with SHUKAH exactly, and the lower-case value gets 422.
  • A design doc says Locate JWTs "short-circuit to verifyClientToken … and were never broken" (docs/plans/2026-07-27-shukah-opaque-token-auth-fix.md:51-52). That was true on 2026-07-27 and has been stale since commit 3396fd1 (2026-07-28): with auth_source set to locate, the JWT branch always gives 401.
  • 2026-10-02: this page said a Shukah token and a Locate JWT both got 200 on dev (2026-10-01), and its Try it expected 200 for either token. On 2026-10-02 dev's auth_source was shukah and dev Shukah no longer accepted the Locate JWT, so only a Shukah token works; with auth_source set to locate every JWT gets 401 because src/services/client.js:12-17 calls .cacheQuery on the Promise returned by the async Client.findByToken (src/models/client.js:163).

Sources​

  • locate-authentication-service@6757aa4: src/routes/user.js:5-12, src/middlewares/user.js:13-275, src/controllers/users/detail/handler.js:9-45, src/services/client.js:5-35, src/models/client.js:163-175, src/services/setting.js:12-51, src/events/settingConsumer.js:14-33, src/shared/constant/auth.js:1-7, src/shared/constant/error-message.js:59,62,77, src/routes/health.js:8, src/controllers/health/handler.js:11-19
  • locate-authentication-service@6757aa4 docs: docs/plans/2026-08-25-point-ledger-shared-write-contract.md:408-421, docs/plans/2026-07-28-settings-broker-consumer.md:43-68, docs/plans/2026-07-27-shukah-opaque-token-auth-fix.md:51-52
  • locate-point-service@2c44ce3: services/api/src/middleware/client-auth.ts:7-18, services/api/src/services/client-auth.service.ts:15-37
  • locate-order-v2-api@d9e4e5f6: src/auth/guards/user-profile-auth.guard.ts:58-72, src/modules/client/orders/orders.controller.ts:84, src/modules/client/gift-card-code/gift-card-code.controller.ts:10, src/modules/client/point/point.controller.ts:17-45 (the point routes have no guard and forward Authorization to the point service)
  • Mobile snapshot mobile/locate-client@e960c3a (branch development, 2026-02-26; the app's own code, not Locate): a search of the whole tree for api/v1/users finds nothing
  • locate-authentication-service@6757aa4: src/config/envConfig.js:22-35
  • Dev captures, 2026-10-02 (parity audit): user-detail--locate-jwt, points--locate-jwt, points--locate-st; the dev client settings and the auth service /health uptime, both read on 2026-10-02
  • Dev captures, 2026-10-01: locate-users-shukah-token, locate-users-locate-token, probe-locate-users, shukah-verify-token