Chuyển tới nội dung chính

OTP login

Bối cảnh​

Trên Shukah, app đăng nhập bằng hai lời gọi. GET /maeda/api/reset-otp gửi mã qua SMS, và GET /maeda/api/verify-otp đổi mã lấy token. Chính lời gọi reset-otp đó cũng đăng ký một client mới. Auth service có cả hai route với cùng tên query như của Shukah, nên việc chuyển phần lớn chỉ là đổi base URL và path prefix. Có ba điều khác cần chú ý: verify-otp trả về JWT của Locate thay vì token Shukah, lỗi của nó có body khác, và nó bỏ qua fcm_token và platform mà app gửi. reset-otp cũng diễn đạt lỗi thiếu phone khác đi.

Tính năng​

Đăng nhập và đăng ký dùng cùng hai lời gọi.

  1. App gọi reset-otp với số điện thoại. Nếu một client active có phone đó, service gửi mã 4 chữ số qua SMS và trả {"status":true}.
  2. Nếu không client active nào có phone đó và lời gọi không mang các field đăng ký, reset-otp trả 400 với Last Name cannot be blank.. Message đó là tín hiệu để app mở màn hình đăng ký. App sau đó gọi lại reset-otp với các field đăng ký, và service tạo client rồi gửi mã.
  3. App gọi verify-otp với phone và mã. Nó nhận một JWT Locate và hồ sơ của client, và gửi JWT đó dưới dạng Authorization: Bearer <token> trên mọi lời gọi sau.

Chưa chuyển đăng nhập sang Locate. Đăng nhập Locate chỉ trả về JWT, mà các route points của order-v2 hiện từ chối JWT. Cho đến khi order-v2 chấp nhận nó, hãy vẫn đăng nhập trên Shukah, lấy JWT bằng exchange-token (target_platform: locate), và giữ cả hai token: JWT cho auth service, token Shukah cho order-v2 (xem Danh sách chuyển đổi).

Trong snapshot app ngày 2026-02-26, đăng nhập gửi phone đã bỏ dấu +, còn đăng ký thêm tên và email, và tùy chọn sex, birth_date và receive_offers. App mở màn hình đăng ký khi reset-otp trả 400 với message có chứa Last Name, và nó lưu access_token mà verify-otp trả về.

Các quy tắc của mã:

Quy tắcGiá trị
Độ dài4 chữ số. Có thể bắt đầu bằng 0.
Hiệu lực3 phút. Chỉ dùng được một lần.
Gửi lạiKhi một mã đang chờ, gọi lại reset-otp không gửi gì và vẫn trả {"status":true}. Chỉ có thể có mã mới sau khi mã cũ hết hạn hoặc đã dùng.
Số lần thửKhông giới hạn số lần nhập sai mã và không có rate limit cho cả hai lời gọi.
Test modeTài khoản test-mode luôn nhận mã 2022, và không gửi SMS. Số nào trên dev là test-mode: Chưa được tài liệu hóa.

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

Thay thế ShukahGET /maeda/api/reset-otp✅ Đã thay thế
Thay thẳng0 breaking · 3 cần lưu ý
  • 🟡 Lỗi 400 thiếu số điện thoại trên Locate là tiếng Anh (Yii), trên Shukah là tiếng Ả Rập (Laravel); app hiển thị message nên chữ thay đổi.
  • 🟡 Số điện thoại chưa tồn tại nhận 400 "Last Name cannot be blank." trên Locate (theo code); nội dung hiện tại của Shukah chưa được ghi lại.
  • 🟡 App chỉ mở màn hình đăng ký nếu message của 400 chứa "Last Name"; hãy xác nhận Shukah và Locate khớp nhau.

Một route phục vụ cả đăng nhập lẫn đăng ký, giống Shukah; trên Locate, số điện thoại chưa tồn tại nhận 400 "Last Name cannot be blank." (tín hiệu để app mở màn hình đăng ký; theo code, còn nội dung hiện tại của Shukah chưa được tài liệu hóa) và receive_offers không được đọc.

Request​

Không cần token. Khi phone thuộc về một client active, các field đăng ký bị bỏ qua.

FieldVị tríKiểuBắt buộcRàng buộcMô tả
contact_phonequerystringcóKhoảng trắng bị bỏ. Nó được so khớp với phone đã lưu có hoặc không có dấu + ở đầu (+966… bằng 966…, nhưng 05… không bằng +9665…). Không kiểm tra độ dài hay định dạng. Trống hoặc thiếu cho 400.Phone để đăng nhập hoặc đăng ký.
last_namequerystringchỉ khi đăng kýCần khi không client active nào có phone. Trống cho 400 Last Name cannot be blank. (kiểm tra đầu tiên). Tối đa 100 ký tự.Họ của client mới.
first_namequerystringchỉ khi đăng kýTrống cho 400 First Name cannot be blank. (kiểm tra thứ hai). Tối đa 100 ký tự.Tên của client mới.
email_addressquerystringchỉ khi đăng kýTrống cho 400 Email Address cannot be blank. (kiểm tra thứ ba). Không kiểm tra định dạng email.Email của client mới.
birth_datequerystringkhôngĐược đọc như một ngày và lưu dạng YYYY-MM-DD. Giá trị không phải ngày sẽ lặng lẽ được lưu rỗng.Ngày sinh của client mới.
sexquery1 hoặc 2không1 là nam và 2 là nữ. Mọi giá trị khác được lưu là không rõ.Giới tính của client mới.
languagequerystringkhôngLưu phần trước dấu -, chữ thường (ar-SA thành ar). Thiếu thì thành ar. Nó không đổi ngôn ngữ SMS, luôn là tiếng Ả Rập.Ngôn ngữ của client mới.
Bất kỳ query key hoặc header nào khácquery, header—khôngBị bỏ qua, ví dụ key. receive_offers cũng không được đọc: client mới bắt đầu với giá trị tắt.—

Response​

200 với cùng một body cho đăng nhập và đăng ký:

FieldKiểuNullableĐịnh dạng / ràng buộcMô tả
statusbooleankhôngLuôn là true.Một mã đã được cấp, hoặc đang có một mã chờ. Nó không xác nhận SMS đã được gửi.
{
"status": true
}

Được capture trên dev, 2026-10-02, với một phone đã biết, trên Locate và trên Shukah: cả hai trả lời body này. Lời gọi đã gửi một SMS. Cùng body này có trong docs/api/client-auth-and-address-book.md §1 (locate-authentication-service@6757aa4, tài liệu thay đổi lần cuối 2026-07-27).

Lỗi​

HTTPBodyKhi nào
400{"name":"Bad Request","message":"Phone number cannot be blank.","code":0,"status":400,"type":"yii\\web\\BadRequestHttpException"}contact_phone thiếu hoặc trống.
400Cùng shape, với message là Last Name cannot be blank., First Name cannot be blank. hoặc Email Address cannot be blank..Không client active nào có phone, và field đăng ký trống đầu tiên (theo thứ tự đó) là field được nêu tên. Một lời gọi đăng nhập trần cho phone không tồn tại nhận Last Name cannot be blank..
500{"status":500,"message":"Internal server error"}Lỗi không mong đợi, ví dụ tên dài hơn 100 ký tự khi đăng ký, lỗi database, hoặc SMS gateway không trả HTTP response. Chưa capture. Ngoài production, message là text lỗi gốc và có thêm field stack.

Body Phone number cannot be blank. được capture trên dev vào 2026-10-01 (lời gọi không có tham số nào). Body Last Name cannot be blank. lấy từ docs/api/client-auth-and-address-book.md §1 (2026-07-27): lần chạy capture không gọi với số điện thoại nào, vì một lời gọi có thể gửi SMS.

Thử ngay​

Một lời gọi thành công gửi SMS thật trừ khi số thuộc về một tài khoản test-mode. Hãy dùng một số test-mode mà bạn kiểm soát. Số nào là test-mode trên dev: Chưa được tài liệu hóa.

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"

Kỳ vọng: 200 và {"status":true}. Không có phone bạn nhận 400 như ở mục Lỗi.

Khác biệt so với Shukah​

Khía cạnhShukahLocateTác động lên app
URLGET $SHUKAH_BASE_URL/maeda/api/reset-otpGET $AUTH_BASE_URL/api/v1/clients/reset-otp. Locate bỏ qua key, mọi query key lạ khác và mọi header.Đổi base URL và path. App có thể tiếp tục gửi key và các header của nó.
Lỗi thiếu phone{"name":"Bad Request","message":"حقل contact phone مطلوب.","code":0,"status":400,"type":"Illuminate\\Validation\\ValidationException"} (capture trên dev, 2026-10-01): message tiếng Ả Rập và type của Laravel.{"name":"Bad Request","message":"Phone number cannot be blank.","code":0,"status":400,"type":"yii\\web\\BadRequestHttpException"} (capture trên dev, 2026-10-01): message tiếng Anh và type của Yii.Các key giống nhau, text và type khác nhau. Đừng so khớp theo text này.
Phone không tồn tại, không có field đăng ký400 với Last Name cannot be blank. trong body kiểu Yii, trong capture tháng 7 năm 2026 lưu ở draft doc của auth service (xem Sources ở mục Chi tiết). Text của Shukah hiện nay: Chưa được tài liệu hóa.400 với Last Name cannot be blank. trong body kiểu Yii.Tín hiệu đăng ký tiếp tục chạy nếu hiện nay Shukah vẫn gửi cùng text. Hãy xác nhận text hiện tại của Shukah trước khi dựa vào nó.
Body thành công{"status":true} (capture trên dev, 2026-10-02).{"status":true} (capture trên dev, 2026-10-02).Không có.

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

Thay thế ShukahGET /maeda/api/verify-otp✅ Đã thay thế
Cần sửa app1 breaking · 3 cần lưu ý
  • 🔴 Chưa chuyển đăng nhập sang Locate: đăng nhập Locate chỉ trả về JWT, mà các route points của order-v2 hiện từ chối JWT này.
  • 🟡 Body 400 khi sai mã là {status,message}; Shukah thêm name/code/type (Laravel); app chỉ đọc message.
  • 🟡 fcm_token và platform bị bỏ qua trên Locate; chưa thấy Shukah xử lý chúng thế nào trong response của nó.
  • 🟡 Gọi thành công trên dev (2026-10-02): cùng {access_token,data} và hồ sơ 13 key ở cả hai phía; session cũ vẫn hợp lệ.

Trả về access_token và profile trong data, trong đó token là một JWT của Locate (body thành công của Shukah có cùng shape, capture 2026-10-02), còn fcm_token và platform bị bỏ qua.

Request​

Không cần token.

FieldVị tríKiểuBắt buộcRàng buộcMô tả
contact_phonequerystringcóKhoảng trắng bị bỏ. Nó được so khớp với phone mà mã được gửi tới, có hoặc không có dấu + ở đầu. Thiếu cho cùng 400 như mã sai.Phone đã dùng trong reset-otp.
codequerystringcóĐược so sánh dạng text với mã 4 chữ số, nên số 0 ở đầu có ý nghĩa.Mã từ SMS.
language, platform, fcm_token và mọi query key khácquerystringkhôngBị bỏ qua.—

Response​

200 kèm token và hồ sơ:

FieldKiểuNullableĐịnh dạng / ràng buộcMô tả
access_tokenstringkhôngMột JWT Locate: ba phần ngăn cách bằng dấu chấm, ký bằng HS256. Nó không bao giờ hết hạn.Gửi nó dưới dạng Authorization: Bearer <access_token> trên mọi lời gọi sau.
dataobjectkhôngHồ sơ của client: 13 field được liệt kê ở Verify token.Client đang đăng nhập.
{
"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
}
}

Shape từ docs/api/client-auth-and-address-book.md §2 (locate-authentication-service@6757aa4, tài liệu thay đổi lần cuối 2026-07-27), và đã gọi live trên dev ngày 2026-10-02 (200, cùng key và giá trị như trên; xem Khác biệt so với Shukah). Giá trị token đã bị che lúc capture nên loại token không được ghi lại ở đây: nhận định JWT lấy từ code. Các giá trị hồ sơ là giá trị minh họa dùng trên toàn site.

Lỗi​

HTTPBodyKhi nào
400{"status":400,"message":"Sorry, but your verification code is incorrect"}Không có mã nào chưa dùng, chưa hết hạn khớp với phone và mã (sai, hết hạn, đã dùng, hoặc thiếu tham số), hoặc không client active nào có phone. Một message duy nhất bao phủ tất cả, có chủ đích, để lời gọi không tiết lộ phone nào có tài khoản.
400Cùng shape, với text lỗi gốc làm message.Bất kỳ lỗi nào khác bên trong handler, ví dụ lỗi database hoặc ghi token thất bại. Chưa capture.

Body đầu tiên được capture trên dev vào 2026-10-01 (lời gọi không có tham số nào), và lại vào 2026-10-02 với một mã sai. Nó không có shape Yii, khác với các lỗi của reset-otp.

Thử ngay​

Một lời gọi thành công dùng hết mã và tạo một token Locate mới. Các token trước đó vẫn hợp lệ.

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"

Kỳ vọng: 200 kèm access_token và data. Mã sai hoặc hết hạn cho 400 như ở mục Lỗi.

Khác biệt so với Shukah​

Khía cạnhShukahLocateTác động lên app
URLGET $SHUKAH_BASE_URL/maeda/api/verify-otpGET $AUTH_BASE_URL/api/v1/clients/verify-otpĐổi base URL và path.
TokenMột token opaque của Shukah.Một JWT Locate trong access_token.Mọi lời gọi /api/v1/clients/* sau đó đều cần JWT này, nhưng các route points của order-v2 hiện từ chối nó, nên tạm thời vẫn đăng nhập trên Shukah.
Query key thêmApp cũng gửi fcm_token (push token của nó, bỏ qua khi app không lấy được) và platform (Android, iOS hoặc Huawei). Shukah làm gì với chúng: Chưa được tài liệu hóa.Locate chỉ đọc contact_phone và code, nên bỏ fcm_token và platform.Lời gọi vẫn chạy. Nếu Shukah có dùng một trong hai giá trị đó, nó sẽ mất trên Locate: hãy xác nhận Shukah đã làm gì với chúng.
Lỗi mã sai{"name":"Bad Request","message":"Sorry, but your verification code is incorrect","code":0,"status":400,"type":"Symfony\\Component\\HttpKernel\\Exception\\HttpException"} (capture trên dev, 2026-10-02). Capture tháng 7 năm 2026 lưu ở draft doc của auth service có cùng message với type kiểu Yii yii\\web\\BadRequestHttpException (xem Sources ở mục Chi tiết).{"status":400,"message":"Sorry, but your verification code is incorrect"} (capture trên dev, 2026-10-01 và 2026-10-02).message và status có ở cả hai, với cùng text. name, code và type không còn, nên đừng đọc chúng.
Body thành công200 {access_token, data}, với data là hồ sơ 13 field (capture trên dev, 2026-10-02, cùng phone và mã với Locate).200 {access_token, data}, với data là cùng 13 field và giá trị bằng nhau (capture trên dev, 2026-10-02).Shape và hồ sơ giống nhau nên phần parse của app không cần đổi. Giá trị access_token đã bị che lúc capture, nên loại token ở mỗi phía không được ghi lại ở đây (dòng Token lấy từ code). Một lần đăng nhập mới không kết thúc các session trước đó ở cả hai phía.

Lưu ý​

  • Lời gọi reset-otp thứ hai trong vòng 3 phút không gửi SMS mới nhưng vẫn trả {"status":true}, nên true không có nghĩa là SMS đã được gửi.
  • Client không active (suspended, blocked, pending hoặc expired) được coi như phone không tồn tại, nên bị đưa tới đăng ký.
  • Body lỗi khác nhau: lỗi của reset-otp có shape Yii (name, message, code, status, type), lỗi của verify-otp chỉ có status và message.

Chi tiết (cho backend)​

Chỉ kiểm tra dev: host https://dev.authentication.locate.sa, prefix /api/v1. Host và prefix của UAT và production: Chưa được tài liệu hóa. Dev không chạy với NODE_ENV=production: body lỗi của nó có field stack (capture 2026-10-01 trên các lỗi address-book, profile và setting), và handler chỉ thêm stack ngoài production (src/middlewares/error-handler.js:12-35).

Quy tắc OTP​

Quy tắcSự kiệnNguồn
Độ dài4 ký tự, chữ số thập phân, có thể bắt đầu bằng 0: Math.random().toString().slice(2, 6).src/services/client-otp.js:7-8
Hiệu lực3 phút kể từ lúc tạo. Verify cần expired_at là bây giờ hoặc muộn hơn.src/services/client-otp.js:5,37; src/controllers/client/verify-otp/handler.js:27
Dùng một lầnVerify set verified: true và verified_at, và mã đã dùng không còn khớp.src/controllers/client/verify-otp/handler.js:26,38-41
Gửi lạiNếu có một mã chưa verify, chưa hết hạn cho phone đã lưu, không có gì được tạo hay gửi và câu trả lời vẫn là {"status":true}.src/services/client-otp.js:20-23
Số lần thử và rate limitKhông có: không có bộ đếm, lockout hay rate limiter.docs/plans/2026-07-28-k6-load-test-suite.md:9
Mã cố định 2022, tài khoản test-modeaccount_mode TEST cho 2022 và không có SMS.src/services/client-otp.js:7-8,40-42; src/shared/constant/user.js:27-30
Mã cố định 2022, hai phoneMột phone đã lưu đúng là +966500000000 hoặc 966500000000 nhận 2022 ở mọi chế độ, và SMS vẫn được gửi trừ khi tài khoản là test-mode. Thêm vào 2026-07-14 (commit 7aad3af). Phone mẫu dùng trên trang này là một trong hai số đó.src/services/client-otp.js:25-27
Cách đặt test modeaccount_mode mặc định là STANDARD. Không API nào set nó: nó được ghi thẳng vào database.src/models/client.js:87-90; docs/plans/2026-07-28-k6-load-test-suite.md:18
Số testKhông có số nào được tài liệu hóa cho dev. Bộ load-test dành riêng các phone test-mode gồm 9665000 cộng sáu chữ số (client id từ 9,000,000,000), được seed thẳng vào database. Các tài khoản như vậy có tồn tại trên database dev dùng chung hay không: Chưa được tài liệu hóa.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
Tra cứu tài khoảnreset-otp, verify-otp và client lookup tìm client theo phone đã lưu với + tùy chọn, chưa bị xóa, status ACTIVE, client_id mới nhất trước, không cache. Một client không active là vô hình với cả ba.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 với Bearer ${SMS_TOKEN}. Nội dung là tiếng Ả Rập bất kể language nói gì. Người nhận 0XXXXXXXXX thành 966XXXXXXXXX, nếu không thì phone đã lưu được gửi dưới dạng số. Response của gateway được lưu trên dòng OTP.src/services/client-otp.js:11-14,30,41; src/shared/helpers/sms.js:4-24
LoggingMã được sinh ra được ghi vào server log.src/services/client-otp.js:29

Đăng ký qua reset-otp​

Khi không client active nào có phone và ba field bắt buộc đều có mặt, handler tạo một client rồi cấp mã (src/controllers/client/reset-otp/handler.js:48-73).

  • Client mới nhận client_id = 1,000,000,000 + counter, source_system và change_origin đặt là locate, from_locate: true, các Shukah id rỗng, client_language, và các mặc định của model: status ACTIVE, mode STANDARD, push bật, offers tắt (src/controllers/client/reset-otp/handler.js:51-69, src/models/counter.js:7,17-24, src/models/client.js:83-90,110-111). Các tên được lưu đã trim và model cho phép 100 ký tự cho mỗi tên (src/models/client.js:45-52).
  • Sau khi mã được cấp, service đẩy client sang Shukah: POST {SHUKAH_HOST}/maeda/client, một upsert theo phone. Nó là best effort và được await, và nó lưu id mà Shukah trả về làm shukah_id. Nó không làm gì khi outbound sync tắt hoặc host hay admin token chưa được set (src/controllers/client/reset-otp/handler.js:71-73, src/services/shukah-sync.js:21-30,184-234).
  • Một phone không active hành xử như phone không tồn tại. Model không khai báo unique index trên phone, nên với đủ các field đăng ký handler sẽ insert một client thứ hai cùng phone, và client đó sẽ thắng ở lookup (client_id cao nhất). Đọc code, chưa test live. Điều này có chủ đích hay không: Chưa được tài liệu hóa (src/models/client.js:147-156).
  • Request chờ SMS gateway (await sendMessage, không có timeout). Một câu trả lời lỗi HTTP của gateway được lưu trên dòng OTP và bị bỏ qua, nên câu trả lời vẫn là {"status":true}. Một lỗi gateway không có HTTP response sẽ throw trong catch và cho 500, sau khi dòng OTP đã được tạo, nên một lần thử lại trong 3 phút không gửi gì (src/shared/helpers/sms.js:18-23; đọc code).

Verify mã​

Thứ tự thao tác (src/controllers/client/verify-otp/handler.js:20-46): tìm mã, tìm client active, mint JWT, insert dòng token_bridge, đẩy JWT sang Shukah, đánh dấu mã đã dùng, trả lời. Mã chỉ bị tiêu thụ sau khi việc ghi token và push đã hoàn tất.

  • Shape thành công {access_token, data} được chốt bằng một unit test (src/controllers/client/__tests__/verify-otp.test.js:77-94).
  • Mỗi lần thành công mint một token mới. Các token trước vẫn hợp lệ vì không gì thu hồi chúng (src/shared/helpers/jwt.js:4-10). Đã xác nhận live trên dev ngày 2026-10-02: sau một lần đăng nhập mới ở mỗi phía, hai token phát hành trước đó vẫn dùng được.
  • Dòng token_bridge có source_system: 'merchant', và token raw chỉ khi NODE_ENV không phải production (src/services/merchant-token-bridge.js:6-16).
  • Push sang Shukah là POST {SHUKAH_HOST}/maeda/client/add-token với {access_token, client_id: <shukah_id>}, best effort và được await. Nó không làm gì khi client không có shukah_id hoặc sync tắt (src/services/shukah-sync.js:13-75). Đây là cách Shukah biết các JWT Locate (docs/plans/2026-07-16-shukah-token-outbound-push.md:10-28). Push có được bật trên dev hay không, và API Shukah nào chấp nhận JWT Locate sau đó: Chưa được tài liệu hóa.
  • Bất kỳ exception nào trong handler (lỗi database, ghi token thất bại, token hash trùng) đều được catch và trả 400 với message gốc (src/controllers/client/verify-otp/handler.js:47-53; đọc code).
  • Hai lời gọi verify cho cùng một client trong cùng một giây sẽ mint một JWT giống hệt từng byte và đụng index token_hash unique, cho 400 đó. Đọc code và một offline determinism check, chưa tái hiện: Chưa được tài liệu hóa là trên dev có xảy ra hay không.
  • Không có bộ đếm số lần thử: mã 4 chữ số có thể được thử lặp lại trong 3 phút của nó.

Khi tài liệu và code mâu thuẫn (code thắng)​

  • Sơ đồ flow kết thúc của tài liệu service nói verify-otp trả {id, token} (docs/api/client-auth-and-address-book.md:542). Code, và chính tài liệu đó ở dòng 110-131, nói {access_token, data}.
  • Tài liệu service nói verify "deletes all OTPs of the phone" (docs/api/client-auth-and-address-book.md:135). Code chỉ đánh dấu mã khớp là đã dùng (commit a5a5315, 2026-07-17).
  • Một design doc nói mã sai cho 400 kiểu Yii (docs/plans/2026-07-06-shukah-auth-api-local.md:101-104). Code và tài liệu API cho {"status":400,"message":...}.
  • Mã test-mode là 2022 trong code. Unit test (src/services/__tests__/client-otp.test.js:6) và một design doc (docs/plans/2026-07-06-shukah-auth-api-local.md:155,213) nói 8888, và unit test đó fail với code.
  • Một design doc nói verify-otp set last_login_at và lưu token trên client (docs/plans/2026-07-06-shukah-auth-api-local.md:106-108). Handler không làm cả hai.
  • Design doc 2026-07-06 và flow chart nháp viết path là /maeda/api/*. Từ commit d837492 (2026-07-10) mọi route client nằm dưới PREFIX_API_V1/clients, và host auth trả 404 cho /maeda/*.
  • 2026-10-02: trang này nói một reset-otp thành công chưa được capture. Vào 2026-10-02 cả Shukah và Locate đều trả lời 200 {"status":true} cho một phone đã biết.
  • 2026-10-02: trang này không có body Shukah hiện tại cho một mã verify-otp sai. Shukah dev trả lời body Laravel với một type của Symfony, hiển thị ở mục Khác biệt so với Shukah.
  • 2026-10-02: trang này nói một verify-otp thành công chưa được capture live, và body thành công của Shukah chưa được tài liệu hóa. Vào 2026-10-02 cả Shukah và Locate đều trả lời 200 {access_token, data} với cùng hồ sơ 13 key và giá trị bằng nhau, và các token trước đó vẫn dùng được sau lần đăng nhập mới.

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
  • Tài liệu của locate-authentication-service@6757aa4: docs/api/client-auth-and-address-book.md §1 và §2 (dòng 45-146, 542; thay đổi lần cuối 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
  • Snapshot mobile mobile/locate-client@e960c3a (branch development, 2026-02-26; code của chính app, không phải 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
  • Capture 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
  • Capture dev, 2026-10-01: probe-locate-reset-otp, probe-locate-verify-otp, probe-shukah-reset-otp, probe-auth-host-maeda-prefix