Bảng tổng hợp API — GIW POC Identity Platform
Trạng thái: POC · Ngày: 21/09/2026
Mọi API và tích hợp trong nền tảng, gói trên một trang.
Cột PII: none · personal · sensitive (dữ liệu cá nhân nhạy cảm theo Luật 91/2025/QH15).
1. Portal API — thứ duy nhất trình duyệt gọi
Bên cung cấp: Wi-Fi Portal. Bên tiêu thụ: chính frontend của portal. Xác thực: cookie mờ sid (HttpOnly, SameSite=Strict, 1 giờ).
| API | PII | Idempotent | Trạng thái | POC / Production |
|---|---|---|---|---|
POST /api/v1/auth/login |
personal (email) + secret (mật khẩu, thoáng qua) | không — mỗi lần gọi là một lượt xác thực mới | ĐÃ LÀM | CHỈ POC. Direct Grant, T-GRANT |
POST /api/v1/auth/register |
personal (email, tên) + secret (mật khẩu) | không — 409 nếu trùng | ĐÃ LÀM | POC. Chưa xác minh email (không có mail server) |
POST /api/v1/auth/employee |
sensitive (CCCD đi vào, không bao giờ đi ra) | có — verify là chỉ đọc | ĐÃ LÀM | POC. T-CCCD |
GET /api/v1/auth/session |
không, ngoài subject | có — chỉ đọc | ĐÃ LÀM | POC |
POST /api/v1/auth/refresh |
không | không — xoay token | ĐÃ LÀM | POC |
POST /api/v1/auth/logout |
không | có — luôn trả 200 | ĐÃ LÀM | POC |
GET /api/v1/meta |
không | có | ĐÃ LÀM | POC |
Trình duyệt chỉ biết một hostname (portal) và giữ một cookie mờ. Nó không bao giờ thấy token, URL của Keycloak, hay địa chỉ của bất kỳ service phía sau nào.
2. Các API service (Galaxy tự xây)
| API | Bên cung cấp | Bên tiêu thụ | Xác thực | PII | Idempotent | Trạng thái | POC / Production |
|---|---|---|---|---|---|---|---|
POST /api/v1/employees/verify |
HR (giả lập) | Keycloak authenticator | X-API-Key |
sensitive (CCCD vào, không bao giờ ra) | có — chỉ đọc | ĐÃ LÀM | POC. Hợp đồng production TBC (T-HR-SOR) |
POST /api/v1/entitlements/evaluate |
Entitlement Service | Wi-Fi Portal | Bearer JWT (RS256, verify iss+aud+exp) | không — chỉ subject id | có — Idempotency-Key |
ĐÃ LÀM | POC. Policy phụ thuộc ADR-011 |
GET /api/v1/entitlements/{id} |
Entitlement Service | Wi-Fi Portal, Ops | Bearer JWT | không | có — chỉ đọc | ĐÃ LÀM | POC |
POST /api/v1/sessions |
Session Service | Wi-Fi Portal | X-API-Key |
không — deviceRef là mờ |
có — Idempotency-Key |
ĐÃ LÀM | POC. Adapter thật theo ADR-007 |
POST /api/v1/sessions/{id}/grant |
Session Service | Wi-Fi Portal | X-API-Key |
không | có — khai báo trạng thái mong muốn | ĐÃ LÀM | POC |
POST /api/v1/sessions/{id}/revoke |
Session Service | Wi-Fi Portal | X-API-Key |
không | có — khai báo | ĐÃ LÀM | POC |
GET /api/v1/sessions/{id} |
Session Service | Wi-Fi Portal, Ops | X-API-Key |
không | có — chỉ đọc | ĐÃ LÀM | POC |
GET /health |
cả bốn service | Ops, Docker | không | không | có | ĐÃ LÀM | POC |
POST /admin/fault |
mock-hr-api | bộ test | không | không | có | ĐÃ LÀM | CHỈ POC — không được tồn tại ở production |
POST /admin/reset-limits |
mock-hr-api | bộ test | không | không | có | ĐÃ LÀM | CHỈ POC — không được tồn tại ở production |
POST /api/v1/decisions/{model}/evaluate |
DMN Service | Entitlement Service | không | không | có — hàm thuần | ĐÃ LÀM | POC. Luật nằm trong .dmn trên đĩa |
GET /api/v1/decisions |
DMN Service | Ops | không | không | có | ĐÃ LÀM | POC |
POST /api/v1/members/lookup |
Loyalty (giả lập) | Entitlement Service | X-API-Key |
personal (email vào, không bao giờ ra) | có — chỉ đọc | ĐÃ LÀM | POC. Làm giàu, không bao giờ là cổng chặn |
POST /api/v1/decisions/reload |
DMN Service | người phụ trách nghiệp vụ / CI | không | không | có | ĐÃ LÀM | ⚠️ không xác thực — xem DMN-DECISION-SPEC.md §7 |
2b. Module giw-admin — tích hợp công ty thành viên
Bên cung cấp: một module tuỳ biến của Keycloak (RealmResourceProvider, id giw-admin), do chính Keycloak phục vụ tại /realms/{realm}/giw-admin. Phạm vi MVP POC: một module giữ cấu hình kết nối, import theo lô, webhook nhận vào và trình biên tập bảng quyết định. ADR-012 ghi lại những gì một MVP sau sẽ tách ra và cái giá phải trả hôm nay.
Xác thực trên /api/*: bearer token của realm mang quyền realm-management/manage-users. Không phải realm-admin — module này cấp phát người dùng, không quản trị realm. Token admin của realm master không được chấp nhận: module xác thực theo đúng realm mà nó đang phục vụ.
| API | PII | Idempotent | Trạng thái | POC / Production |
|---|---|---|---|---|
GET /giw-admin/ |
không | có | ĐÃ LÀM | POC. Vỏ GUI, công khai; mọi lệnh /api bên dưới đều xác thực |
GET /giw-admin/api/companies |
không | có — chỉ đọc | ĐÃ LÀM | POC. Trả hasSecret, không bao giờ trả secret |
PUT /giw-admin/api/companies/{code} |
không | có — thay thế toàn bộ | ĐÃ LÀM | POC. Lưu bằng realm attribute — không phiên bản, không review (ADR-012) |
DELETE /giw-admin/api/companies/{code} |
không | có | ĐÃ LÀM | POC. Chỉ xoá cấu hình; user đã import vẫn giữ nguyên |
POST /giw-admin/api/companies/{code}/test |
không | có — thăm dò chỉ đọc | ĐÃ LÀM | POC. GET {origin-của-endpoint}/health, báo UP / HTTP_n / UNREACHABLE |
POST /giw-admin/api/import?mode=preview |
sensitive (CCCD vào, không ra, không lưu) | có — không ghi gì | ĐÃ LÀM | POC. So từng dòng với realm ở trạng thái hiện tại |
POST /giw-admin/api/import?mode=commit |
sensitive (CCCD vào, băm ngay khi nhận) | không — tạo và liên kết user | ĐÃ LÀM | POC. T-USER-SYNC — sao chép hay liên kết vẫn còn mở |
POST /giw-admin/webhook/{company} |
personal (mã tham chiếu nhân viên) | có — chống trùng theo X-GIW-Event-Id |
ĐÃ LÀM | POC. Chống trùng trong bộ nhớ; T-WEBHOOK-DURABILITY |
GET /giw-admin/api/events |
personal (payload của sự kiện) | có — chỉ đọc | ĐÃ LÀM | POC. Ring buffer 200 bản ghi, mất khi restart |
GET /giw-admin/api/dmn/models |
không | có | ĐÃ LÀM | POC. Chuyển tiếp sang dmn-service |
GET /giw-admin/api/dmn/models/{name} |
không | có | ĐÃ LÀM | POC |
PUT /giw-admin/api/dmn/models/{name} |
không | có — thay thế toàn bộ | ĐÃ LÀM | POC. Không có quy trình duyệt — cùng khoảng trống với /reload, xem DMN-DECISION-SPEC §7 |
POST /giw-admin/api/dmn/evaluate/{name} |
không | có — chỉ đọc | ĐÃ LÀM | POC. Để thử một thay đổi luật trước khi lưu |
⚠️ Cấu hình kết nối chưa nối vào luồng đăng nhập. Authenticator của nhân viên đọc
hrEndpointtừauthenticatorConfigcủa flow, không đọc từgiw.company.{code}. Sửa endpoint ở đây không đổi được địa chỉ Keycloak gọi lúc đăng nhập. Muốn nối lại cần chốt T-HR-ROUTING; theo dõi ởDEFERRED-TASKSD5.
Những gì module cố ý không làm
| Không làm | Vì sao |
|---|---|
| Lưu CCCD | Các dòng được khớp bằng HMAC-SHA256(cccd, salt của realm). Số gốc bị bỏ sau khi băm. Cái giá: không tra cứu được người theo CCCD, và xoay salt thì mất toàn bộ liên kết |
Đặt employee_verified khi import |
Import là nạp dữ liệu, không phải xác minh. Chỉ một lượt hỏi HR trực tiếp lúc đăng nhập mới được đặt |
| Áp dụng sự kiện không thuộc danh tính | MEMBER_TIER_CHANGED và những loại tương tự chỉ được ghi nhận, không áp dụng. Ghi hạng hội viên vào Keycloak là biến nó thành bản sao cũ của hệ loyalty |
| Ghi đè khi trùng CCCD | Cùng CCCD dưới một employee_ref khác sẽ trả CONFLICT. Có thể là tái tuyển dụng, có thể là gõ nhầm, có thể là một dòng sắp chiếm tài khoản của người khác — ghi đè im lặng là kết cục duy nhất giấu mất đó là trường hợp nào |
| Trả về secret của webhook | Chỉ ghi được. API cho biết đã đặt hay chưa, không bao giờ cho biết giá trị |
Hợp đồng webhook
POST /realms/{realm}/giw-admin/webhook/{company}
| Header | Bắt buộc | Nghĩa là gì |
|---|---|---|
X-GIW-Timestamp |
có | Unix seconds. Từ chối nếu lệch quá ±5 phút |
X-GIW-Signature |
có | base64(HMAC-SHA256(secret, "{timestamp}.{rawBody}")). Ký trên đúng byte đã gửi — serialize lại trước khi ký là kiểm tra bộ serialize của chính mình, không phải kiểm tra bên gửi |
X-GIW-Event-Id |
nên có | Chống trùng. Thiếu nó thì một lần retry của nhà cung cấp sẽ được áp dụng hai lần |
| Loại sự kiện | Tác động |
|---|---|
EMPLOYEE_TERMINATED |
Vô hiệu hoá tài khoản, employee_verified=false |
EMPLOYEE_SUSPENDED |
Vô hiệu hoá tài khoản |
EMPLOYEE_REINSTATED |
Bật lại tài khoản. Không khôi phục trạng thái đã xác minh |
EMPLOYEE_COMPANY_CHANGED |
Cập nhật attribute company |
| mọi loại khác | RECORDED — ghi nhận, không áp dụng |
| Kết quả | HTTP | Nghĩa là gì |
|---|---|---|
APPLIED |
200 | Đã thay đổi danh tính |
RECORDED |
200 | Đã nhận, nhưng không phải sự kiện danh tính |
DUPLICATE |
200 | Event id đã xử lý rồi |
NO_MATCH |
200 | Không user nào mang employeeRef đó — ghi nhận để xem lại |
CONFLICT |
409 | Nhiều hơn một user khớp |
REJECTED |
401 / 400 | Sai chữ ký, timestamp quá hạn, chưa cấu hình secret, hoặc thiếu employeeRef |
3. OAuth / OIDC chuẩn (không do Galaxy xây — không đặc tả lại)
Toàn bộ là server-to-server. Không có redirect trình duyệt nào trong nền tảng này.
| Endpoint | Grant / phương thức | Bên tiêu thụ | Client | PII | Trạng thái |
|---|---|---|---|---|---|
POST /protocol/openid-connect/token |
password |
Wi-Fi Portal | wifi-portal-api (confidential) |
secret trên đường truyền | CHUẨN — RFC 6749 §4.3 |
POST /protocol/openid-connect/token |
password + employeeId + citizenId |
Wi-Fi Portal | wifi-portal-employee-api (confidential) |
sensitive trên đường truyền | Endpoint CHUẨN, flow direct-grant tuỳ biến |
POST /protocol/openid-connect/token |
refresh_token |
Wi-Fi Portal | cả hai | không | CHUẨN — RFC 6749 §6 |
POST /protocol/openid-connect/token |
client_credentials |
Wi-Fi Portal | service account của wifi-portal-api |
không | CHUẨN — RFC 6749 §4.4 |
POST /admin/realms/{realm}/users |
Bearer (service account) | Wi-Fi Portal | giới hạn ở manage-users |
personal | CHUẨN KEYCLOAK ADMIN |
POST /protocol/openid-connect/logout |
back-channel, refresh_token |
Wi-Fi Portal | cả hai | không | CHUẨN — kiểu RFC 7009 |
GET /protocol/openid-connect/userinfo |
Bearer | Wi-Fi Portal | cả hai | personal | CHUẨN OIDC |
GET /protocol/openid-connect/certs |
không (khoá công khai) | Entitlement Service | — | không | CHUẨN OIDC |
| Keycloak Admin REST | admin password grant | bootstrap (chạy một lần) | admin-cli |
personal (user test) | CHUẨN KEYCLOAK |
Các client
| Client | Loại | Grant | Ghi đè flow | Dùng cho |
|---|---|---|---|---|
wifi-portal-api |
confidential | password, client_credentials | direct grant dựng sẵn | Đăng nhập khách hàng, đăng ký |
wifi-portal-employee-api |
confidential | password | direct_grant → employee-direct-grant |
Xác minh nhân viên |
wifi-portal |
public | (standard flow, portal không dùng) | — | Giữ lại chỉ để tham chiếu |
wifi-portal-employee |
public | (standard flow, portal không dùng) | browser → employee-browser |
Giữ lại chỉ để tham chiếu |
Confidential chứ không phải public: một public client bật Direct Access Grants cho phép bất kỳ ai trên mạng phát lại grant mà không cần xác thực client nào. Portal chạy phía server nên nó giữ được secret.
4. Các lệnh gọi nội bộ không có hợp đồng HTTP
| Từ | Tới | Cơ chế | Ghi chú |
|---|---|---|---|
| Custom authenticator | Kho user của Keycloak | SPI UserProvider |
Ghi user_type, employee_verified, employee_ref, company. Không bao giờ ghi CCCD. |
| Keycloak | event log | Keycloak events | Chỉ ghi giw_correlation_id và giw_employee_ref. |
5. Callback / tích hợp nội bộ
Một chiều vào duy nhất: POST /giw-admin/webhook/{company} ở §2b. Không có hàng đợi tin nhắn, không có callback đi ra — mọi chặng khác đều là request đồng bộ do portal khởi xướng.
Đợt review kiến trúc (ADR-009) yêu cầu đường thành công ở production phải được xác nhận bằng callback backend, không bao giờ chỉ bằng redirect trình duyệt. Yêu cầu đó chưa chạm tới đây — chưa có thanh toán hay chiến dịch bên ngoài nào trong phạm vi. Webhook đã đáp ứng các trường dưới đây, trừ đệm khi retry: nó được xử lý trực tiếp trong Keycloak, nên một sự kiện đến đúng lúc restart sẽ mất chứ không vào hàng đợi (T-WEBHOOK-DURABILITY). Mọi callback thêm về sau phải mang theo:
| Trường | Mục đích |
|---|---|
correlationId |
Nối callback về đúng hành trình đã khởi xướng |
subjectId |
Callback này nói về ai |
sessionId |
Nó ảnh hưởng phiên truy cập nào |
Idempotency-Key / X-Event-Id |
Chống trùng — nhà cung cấp nào cũng retry |
| Chữ ký HMAC trên raw body + timestamp | Tính xác thực; cửa sổ ±5 phút |
| Chính sách retry | Phía nhà cung cấp, bên nhận trả 200 trước khi xử lý bất đồng bộ |
6. Timeout và retry
| Lệnh gọi | Timeout | Số lần | Thử lại có an toàn? |
|---|---|---|---|
| Authenticator → HR verify | 3000 ms | 2 | Có — chỉ đọc, không đổi trạng thái |
| Portal → Galaxy ID, grant khách hàng | 5000 ms | 1 | Không — xác thực lại là việc của người dùng |
| Portal → Loyalty | 2000 ms | 1 | Có — chỉ đọc. Ngắn có chủ đích: nó nằm trên đường đăng nhập và là tuỳ chọn. Mọi thất bại đều suy giảm thành "không có hạng", không bao giờ thành lỗi |
| Portal → Galaxy ID, grant nhân viên | 12000 ms | 1 | Có — chỉ đọc. Phải lớn hơn hrTimeout × hrAttempts, nếu không portal hết giờ trước và quy sai một sự cố của HR thành lỗi của Galaxy ID |
| Portal → Entitlement | 5000 ms | 1 | Có — có idempotency key |
| Portal → Session create | 5000 ms | 1 | Có — có idempotency key |
| Portal → Session grant | 5000 ms | 1 | Có — khai báo trạng thái mong muốn |
| Entitlement → JWKS | 4000 ms | 1 + 1 lần refresh cưỡng bức khi gặp kid lạ |
Có |
Mọi giá trị đều là GIẢ ĐỊNH CỦA POC. Xem API-OVERVIEW.md §7.
7. Giới hạn tần suất
| Bề mặt | Giới hạn | Cơ chế |
|---|---|---|
| HR verify — theo từng hồ sơ nhân viên | 5 lần thất bại / 60 s | Khoá theo employeeId, không theo địa chỉ bên gọi: Keycloak là bên gọi duy nhất, nên giới hạn theo IP sẽ bóp cả một khoang hành khách qua chung một rổ. Lần xác minh thành công không bị tính — chúng không phải là đoán mò |
| HR verify — trần toàn cục | 600 req / 60 s | chỉ để chặn vòng lặp mất kiểm soát |
| Form nhân viên | 5 lần submit mỗi phiên xác thực | cấu hình authenticator |
| Đăng nhập Keycloak | 5 lần thất bại rồi giãn tới 900 s | brute-force protection của realm |
| Bùng phát quick-login | 2 lần thất bại trong 200 ms → giữ 60 s | quickLoginCheckMilliSeconds của realm. Trước đây là 1000 ms, khiến một hành khách chỉ bấm Đăng nhập hai lần liên tiếp đã bị khoá. |
| Portal API | không có | Khoảng trống — portal giờ là bề mặt hứng brute-force |
| Entitlement, Session | không có | Khoảng trống — xem API-REVIEW-CHECKLIST.md |