logiSA 2.0 / DOCS

Design baseline · 24 SEP 2026 · 09 / 23

TDD · Login & quản lý Zalo

Current behavior · Dashboard authentication, phiên Zalo, QR, nhóm và transport.

Phạm vi trang: mô tả hành vi có trong source hiện tại để làm nền TDD bảo trì. Đây không phải contract `/v2` của kiến trúc đích. Scenario ghi “có test source” là đã có test code; tài liệu này không khẳng định test đã chạy trong phiên review.

Ranh giới và chủ sở hữu

Dashboard Express sở hữu xác thực người vận hành và BFF. TypeScript gateway sở hữu vòng đời session Zalo, credentials, listener, nhóm và gửi tin. Quarkus nhận event qua internal API để xử lý nghiệp vụ. Request internal gateway yêu cầu X-Internal-Token; browser đi qua BFF đã xác thực bằng session của ứng dụng.

01Đăng nhập DashboardPOST /api/auth/login → cookie zalo_session
02Tạo phiên ZaloBFF chuyển yêu cầu sang gateway; gateway đăng ký channel scope với core
03Quét QRUI poll status; gateway nhận callback từ Zalo SDK và cất credentials
04Nhận / gửiListener chuẩn hóa event, inbox lưu bền; outbound dùng command ID

Điều kiện trước

Contract xác thực ứng dụng

EndpointRequest / response quan sát từ sourceLỗi và biên
POST /api/auth/login{username,password}; trả {success:true,user}, đặt cookie zalo_session httpOnly, SameSite=Strict, Secure khi COOKIE_SECURE=true, TTL 30 ngày.Thiếu trường: 400; sai thông tin: 401; sau 5 lần sai, request tiếp theo trong cửa sổ 2 phút trả 429 + Retry-After. Bộ đếm theo IP là in-memory.
GET /api/auth/meĐọc bearer token hoặc cookie; trả user đã sanitize.Token không hợp lệ/hết hạn: 401.
POST /api/auth/logoutXóa token session, đóng websocket cùng token, xóa cookie.Trả success kể cả khi không có session token.
GET /api/sessionsBFF lấy session của tenant xác thực từ gateway; sau đó đọc status snapshot từng session.Gateway không cấu hình/không kết nối: lỗi được proxy về; snapshot lỗi có thể rơi về metadata/status fallback.
POST /api/users/{tenantId}/sessionsBody chỉ nhận tên hiển thị (1–120 ký tự); tenant suy ra từ principal, admin có thể chọn tenant theo kiểm tra BFF.Sai tenant: 404; tên không hợp lệ: 400; gateway/core lỗi được proxy.
POST /api/sessions/{id}/login/qrBFF gọi internal gateway với tenant lấy từ session principal; gateway trả nhanh trạng thái QR_GENERATING.Chưa chọn session khi nhiều session trong allowlist: 409; chưa có facade: 503; lỗi upstream: 502.

Contract internal gateway cho QR/session

RouteHành vi / responseLỗi chính
GET /internal/v1/channel-sessions?tenantId=…Yêu cầu internal token; liệt kê runtime session theo tenant.401 thiếu/sai token; 400 thiếu tenantId; 404 tenant ngoài active allowlist.
POST /internal/v1/channel-sessionsBody {tenantId,name}; tạo sess_<uuid>, lưu metadata connect_on_start=false, đăng ký channel scope Quarkus, trả 201 + session.400 body sai; 403 tenant không được phép; 502 registration lỗi; 503 lifecycle/session creation lỗi. Nếu registration lỗi, session vừa tạo được soft-delete.
POST /{id}/login/qrBắt đầu login bất đồng bộ; trả success và status hiện tại (thường QR_GENERATING, có thể đã tiến QR_WAITING nếu callback tới nhanh).400 thiếu tenant; 409 session đang connected; 404 không tìm thấy session; 503 lifecycle chưa nối.
GET /{id}/status?tenantId=…Trạng thái, profile, ảnh QR dạng data URI và scanned user (nếu có). Không trả cookie/IMEI/token login.400 thiếu tenant; 404 session không tồn tại/xóa; 503 lifecycle chưa nối.
POST /{id}/login/qr/abortHủy QR, xóa QR state, status DISCONNECTED; connect_on_start=false.404 session không có; 503 lifecycle chưa nối.
POST /{id}/logoutDừng listener, flush album queue, đặt DISCONNECTED và connect_on_start=false; giữ file credentials.404 session không có; 503 lifecycle chưa nối.
DELETE /{id}Logout rồi soft-delete metadata thành tombstone DELETED.404 không tồn tại/đã xóa; credentials không bị xóa.
GET /{id}/groups?tenantId=…&sessionId=…ID path và query phải khớp; gọi getAllGroups() rồi getGroupInfo(); trả groupId/name/desc/member/avatar/time.400 scope fields sai; 404 tenant/session không được cấu hình; 502 lỗi provider hoặc session chưa connected.

Luồng QR và trạng thái

Gateway chỉ giữ pollable QR state trong memory. Callback QR generated chuyển QR_WAITING và thêm data URI; scanned chuyển QR_SCANNED và thêm displayName/avatar; expired xóa QR và về DISCONNECTED; declined đặt ERROR và xóa QR. Khi login promise hoàn tất, adapter trích IMEI/cookie/user-agent/language từ SDK context, ghi file an toàn, lấy profile nếu được, đặt CONNECTED và gắn listener. Lỗi khi hoàn tất đặt ERROR.

Từ trạng tháiSự kiệnTới trạng thái / side effect
DISCONNECTED / ERRORBắt đầu login QRQR_GENERATING; tắt connect_on_start; QR cũ bị xóa.
QR_GENERATINGSDK phát QRCodeGeneratedQR_WAITING + data URI được hiển thị.
QR_WAITINGSDK báo QRCodeScannedQR_SCANNED + scannedUser nếu QR state còn tồn tại.
QR_WAITING / QR_SCANNEDSDK báo hết hạnDISCONNECTED, qrState=null.
QR_WAITING / QR_SCANNEDSDK bị từ chốiERROR, qrState=null.
QR_SCANNEDSDK login resolve và ghi secret thành côngCONNECTED; lưu profile best-effort; connect_on_start=true; listener bắt đầu.
QR_*AbortDISCONNECTED; callback abort; generation tăng để callback cũ không ghi đè.
CONNECTEDLogoutDISCONNECTED; dừng listener; giữ credentials; connect_on_start=false.
Giới hạn vòng đời: nhà cung cấp Zalo không có QR polling token có thể resume. Khi gateway khởi động lại, trạng thái QR_GENERATING/QR_WAITING/QR_SCANNED trong DB được đổi DISCONNECTED; người vận hành phải bắt đầu QR lại. QR không được lưu bền.

Bảo vệ credentials và tenant scope

Transport lân cận: inbound, album, outbound

  1. Zalo message listener bỏ qua message tự gửi, chuẩn hóa chat.photo và nội dung text thành ChannelEvent gồm tenant/session/thread/sender/media/timestamp.
  2. Ảnh liền nhau cùng sender/thread được gom thành một album event theo quiet window; gateway inbox commit trước rồi forward Quarkus. Event ID và channel message ID dedupe trong scope. Core 502/503 được retry có giới hạn; event đã commit được worker replay sau outage/restart; payload conflict trả 409.
  3. Outbound internal command validate text/media/HTTPS URL, tối đa 12 ảnh; idempotency theo tenant+session+commandId. Gateway ghi SENDING trước adapter send, COMPLETED khi có kết quả; lỗi/khởi động lại lúc SENDING thành UNKNOWN và không tự gửi lại.
  4. Image downloader giới hạn domain qua MEDIA_PROXY_HOSTS, bytes/ảnh theo giới hạn SDK và kiểm tra ảnh trước upload. Gửi text + album có thể thành nhiều tin Zalo; kết quả không chắc chắn cần đối soát history, không retry tự động.

TDD scenarios

IDScenario / expected observable behaviorEvidence status
ZL-01Login app thành công đặt cookie an toàn; /me nhận user đã sanitize.Có test/source: app auth source; gateway suite không bao phủ auth app đầy đủ.
ZL-02Login thiếu username/password → 400; 5 lần sai → 401; request sai tiếp theo trong cửa sổ → 429 và Retry-After.Có logic source; cần test tích hợp app để xác nhận boundary/rate-limit expiry.
ZL-03Không cookie/bearer hoặc token hết hạn: protected /api trả 401; logout invalidates token và clears cookie.Source-derived; scenario test đề xuất nếu chưa có test route.
ZL-04Tạo session thành công tạo sess UUID, đăng ký core scope; registration 403/5xx làm session tombstone và không để session dùng được.Có test gateway lifecycle phần 201/tenant error; cần test rollback registration lỗi.
ZL-05Unauthenticated internal call → 401; tenant inactive → 403 create / 404 list; tenant B không thể đọc session tenant A.Có test source: gateway/tests/gateway.test.ts lifecycle/scope.
ZL-06QR start trả nhanh QR_GENERATING; poll thấy QR_WAITING image; scan hiện QR_SCANNED; success CONNECTED.Có test source: gateway/tests/zalo-lifecycle.test.ts mock QR lifecycle.
ZL-07Status tuyệt đối không chứa QR token/code, cookie, IMEI hay user-agent; credentials file mode 0600, dirs 0700.Có test source: lifecycle và session-secrets.
ZL-08Abort/expire/decline/logout/delete theo status transitions; logout giữ secret + connect_on_start=false; delete là tombstone.Có test source: lifecycle; expiry/decline branches cần kiểm chứng thêm.
ZL-09Gateway restart khi QR chưa xong reconcile status về DISCONNECTED; bắt đầu QR mới hoạt động.Có logic source ở registry initialize; cần integration test restart DB + gateway.
ZL-10Connected session lấy group inventory; mismatch path/query session → 400; cross-tenant/not-connected → không trả nhóm.Có test source cho scope/auth; cần adapter test cho provider failure/not connected.
ZL-11Inbound album giữ thứ tự ảnh và tạo đúng một event trong debounce window; gửi ảnh đơn thì event media có một phần tử.Có test source: gateway/tests/zalo-image-album.test.ts, zalo-media.test.ts.
ZL-12Inbound DB down không trả ack QUEUED; Java outage sau commit trả queued rồi replay theo thứ tự session.Có test source: inbound-store.test.ts.
ZL-13Outbound duplicate cùng payload không gọi adapter lần nữa; command ID reuse payload khác → 409; outcome UNKNOWN không tự replay.Có test source: gateway/tests/gateway.test.ts + command-store behaviors.
ZL-14Media HTTP, non-allowlisted host, quá số lượng/bytes hoặc URL sai protocol bị từ chối trước adapter upload.Có test source: gateway/tests/zalo-media.test.ts; bổ sung boundary tests theo cấu hình thực tế.
ZL-15Hai tenant import cùng session ID nhưng chỉ có flat legacy secret.Không dùng chung credential; flat legacy secret bị từ chối, mỗi tenant cần secret scoped riêng. Test source: gateway/tests/session-config.test.ts; trạng thái chạy suite được ghi sau lượt kiểm tra.

Kết quả chạy test · 24 Sep 2026

Bộ kiểm traKết quảPhạm vi được chứng minh
bun run app:typecheckPASSTypeScript cho Dashboard/BFF.
bun run app:testPASS · 40/40Bộ app, gồm 9Router cookie retry và workflow BFF. Chưa có kiểm thử end-to-end login app với Zalo thật.
bun test gateway/tests/*.test.tsPASS · 31/31Gateway session/QR, secret scope, album, inbox và outbound bằng mock/fake.

Các kết quả này xác nhận contract trong code/test harness; chúng không thay thế login QR bằng tài khoản thật, đọc inventory group thật hoặc kiểm tra quyền gửi tại Zalo.

Khoảng trống và không được suy diễn

Source of truth để bảo trì

backend/dashboard-bff/auth.ts; route/BFF trong backend/dashboard-bff/server.ts; gateway/server.ts; gateway/adapters/zalo.ts; gateway/session-store.ts; gateway/session-config.ts; gateway/session-secrets.ts; gateway/adapters/zalo-image-album.ts; gateway/adapters/zalo-media.ts; gateway/adapters/zalo-media-upload.ts; gateway/inbound-store.ts; gateway/command-store.ts. Test source: gateway/tests/{gateway,zalo-lifecycle,session-config,session-secrets,zalo-image-album,zalo-media,inbound-store}.test.ts. App login/BFF boundary and real Zalo account/group permissions still need runtime acceptance beyond these gateway tests.

logiSA 2.0 · Public development cookbook · Design target; implementation status in Delivery.