Đăng nhập
Xác thực và đăng nhập vào hệ thống thông qua quy trình 2 bước (thêm bước xác thực 2 lớp khi tài khoản bật 2FA).
Trên Hubport, một tài khoản (email) có thể là nhân viên của nhiều công ty (cửa hàng). Vì vậy đăng nhập gồm 2 bước: bước 1 xác thực email/mật khẩu và lấy danh sách công ty, bước 2 chọn công ty để nhận JWT Token. Mỗi JWT Token chỉ gắn với một công ty; để chuyển sang công ty khác sau khi đăng nhập, dùng Đổi công ty đang làm việc.
Hai API đăng nhập không cần header Authorization.
Bước 1: Khởi tạo đăng nhập (authenticateInit)
Xác thực email/password và lấy danh sách các công ty mà người dùng tham gia.
Endpoint: POST /v1/users/authenticateinit
Body Parameters:
| Tên | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
email | string | Có | Email người dùng |
password | string | Có | Mật khẩu |
Response Schema:
{
"total": 2,
"limit": 20,
"currentpage": 1,
"items": [
{
"id": 101,
"user_id": 1,
"company": {
"user_id": 1,
"id": 10311,
"name": "Công ty TNHH Phần Mềm Xanh",
"domain": "phanmemxanh",
"screenname": "phanmemxanh",
"status": 1,
"plan": 2,
"date_created": 1609459200,
...
},
"employee": {
"company_id": 10311,
"user_id": 1,
"id": 101,
"full_name": "Nguyễn Văn A",
"email": "[email protected]",
"phone": "0987654321",
"status": 1,
...
},
"access": "full"
},
{
"id": 205,
"user_id": 1,
"company": {
"user_id": 9,
"id": 10500,
"name": "Chi nhánh Hà Nội",
"domain": "pmx-hanoi",
"status": 1,
...
},
"employee": {
"company_id": 10500,
"user_id": 1,
"id": 205,
"full_name": "Nguyễn Văn A",
"status": 1,
...
},
"access": "blocked"
}
]
}Response Fields (mỗi phần tử của items):
| Field | Mô tả |
|---|---|
id | Employee ID của người dùng trong công ty |
user_id | User ID |
company | Thông tin công ty |
employee | Thông tin nhân viên trong công ty |
access | (Chỉ có trên Hubport SaaS) Trạng thái truy cập công ty: full (đầy đủ), limited (giới hạn tính năng, ví dụ hết thời gian dùng thử), blocked (công ty bị khoá, không đăng nhập được), restorable (công ty đã xoá nhưng còn khôi phục được) |
Lưu ý:
- Danh sách trả về tối đa 20 công ty.
- Chỉ gồm các công ty người dùng đang là nhân viên; công ty đã xoá người dùng khỏi danh sách nhân viên không xuất hiện (và không đăng nhập được ở bước 2).
- Nếu người dùng chỉ thuộc 1 công ty, ứng dụng có thể gọi luôn bước 2 với
company_idcủa công ty đó. - Email sẽ được chuyển về lowercase trước khi xử lý.
Bước 2: Hoàn tất đăng nhập (authenticateFinish)
Xác thực và lấy JWT token cho công ty đã chọn.
Endpoint: POST /v1/users/authenticatefinish
Body Parameters:
| Tên | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
email | string | Có | Email người dùng |
password | string | Có | Mật khẩu |
company_id | int | Có | ID công ty (từ bước 1) |
source | string | Nguồn đăng nhập, dùng để hiển thị trong danh sách phiên đăng nhập: frontend (mặc định), frontend-pos hoặc api. Giá trị khác được ghi nhận là frontend |
Response Schema:
{
"jwt": "0123456789abcdef0123456789abcdef",
"refresh_token": "a1b2c3d4e5f6...",
"date_expired": 1641920399,
"id": 1,
"email": "[email protected]",
"full_name": "Nguyễn Văn A",
"role": "1,100,102,103,110",
"company": {
"user_id": 1,
"id": 10311,
"name": "Công ty TNHH Phần Mềm Xanh",
"domain": "phanmemxanh",
"status": 1,
"plan": 2,
"date_created": 1609459200,
...
},
"employee": {
"company_id": 10311,
"user_id": 1,
"id": 101,
"full_name": "Nguyễn Văn A",
"email": "[email protected]",
"phone": "0987654321",
"status": 1,
...
},
"company_list": [
{
"id": 101,
"user_id": 1,
"company": { "id": 10311, "name": "Công ty TNHH Phần Mềm Xanh", ... },
"employee": { ... }
}
],
"access": "full",
"access_state": "paid"
}Response Fields:
| Field | Mô tả |
|---|---|
jwt | JWT token để sử dụng cho các request tiếp theo (gửi trong header Authorization) |
refresh_token | Refresh token để làm mới JWT trước khi hết hạn (dùng một lần) |
date_expired | Thời điểm hết hạn của phiên (UNIX timestamp) |
id | User ID |
email | Email người dùng |
full_name | Tên đầy đủ của nhân viên trong công ty |
role | Danh sách mã quyền (ID) của nhân viên trong công ty, phân cách bằng dấu phẩy. Chủ công ty có toàn bộ quyền |
company | Thông tin công ty đang đăng nhập |
employee | Thông tin nhân viên trong công ty |
company_list | Danh sách công ty người dùng đang là nhân viên (mỗi phần tử gồm id, user_id, company, employee); không gồm công ty đã xoá người dùng khỏi danh sách nhân viên |
access | (Chỉ có trên Hubport SaaS) Trạng thái truy cập công ty: full, limited hoặc restorable (xem Bước 1) |
access_state | (Chỉ có trên Hubport SaaS) Trạng thái gói dịch vụ của công ty, ví dụ trial (dùng thử), paid (đã trả phí), past_due (quá hạn thanh toán, đang trong thời gian ân hạn), free (miễn phí), soft_deleted (đã xoá, có thể khôi phục) |
Bước 3 (khi cần): Xác thực 2 lớp
Tài khoản đã bật xác thực 2 lớp (2FA, mã 6 số từ ứng dụng xác thực như Google Authenticator), công ty bắt buộc mọi nhân viên
dùng 2FA, hoặc người đăng nhập là chủ shop có kết nối sàn (Shopee / Lazada / TikTok Shop đang hoạt động, Hubport SaaS), thì
bước 2 không trả JWT dù mật khẩu đúng. Response là HTTP 401 kèm mã tạm challenge:
{
"error": ["error_two_factor_required"],
"detail": {
"challenge": "Q2hhbGxlbmdlVG9rZW5FeGFtcGxlRXhhbXBsZUFC123",
"expires_in": 300,
"purpose": "verify",
"methods": ["totp", "recovery_code"],
"company_required": false,
"marketplace_required": false,
"required_reason": ""
}
}Trường detail | Mô tả |
|---|---|
company_required | Công ty bắt buộc 2FA cho mọi nhân viên |
marketplace_required | Người đăng nhập là chủ shop có kết nối sàn đang hoạt động và chưa bật 2FA (chỉ khi purpose = "enroll") |
required_reason | Lý do phải thiết lập khi purpose = "enroll": company_policy (công ty bắt buộc) hoặc marketplace_owner (chủ shop có kết nối sàn). Rỗng khi purpose = "verify". Ứng dụng nên hiện lời giải thích tương ứng |
-
purpose = "verify"(lỗierror_two_factor_required): gửi mã 6 số đang hiện trong ứng dụng xác thực, hoặc một mã khôi phục (dạngxxxxx-xxxxx, mỗi mã dùng một lần).Endpoint:
POST /v1/users/twofactorchallenge/verify, body{"challenge": "...", "code": "123456"}. -
purpose = "enroll"(lỗierror_two_factor_enroll_required): công ty bắt buộc 2FA, hoặc chủ shop có kết nối sàn, mà tài khoản chưa bật. Người dùng phải thiết lập trước khi có JWT:POST /v1/users/twofactorchallenge/enrollinit, body{"challenge": "..."}, trả{"secret", "otpauth_url", "expires_in"}. Hiệnotpauth_urldạng mã QR để người dùng quét bằng ứng dụng xác thực.POST /v1/users/twofactorchallenge/enrollfinish, body{"challenge": "...", "code": "123456"}. Response có thêmrecovery_codes(10 mã khôi phục, chỉ trả một lần: ứng dụng phải hiện cho người dùng lưu lại).
Thành công: response giống bước 2 (jwt, refresh_token, company, ...) kèm two_factor_method (totp hoặc recovery_code).
Mã challenge hết hạn sau expires_in giây và chỉ dùng được một lần; hết hạn thì gọi lại bước 2.
Ba API này không cần header Authorization.
| HTTP | Code | Mô tả |
|---|---|---|
| 404 | error_two_factor_challenge_invalid | challenge sai, hết hạn hoặc đã dùng (đăng nhập lại từ bước 2) |
| 409 | error_two_factor_already_enabled | (enroll) Tài khoản đã bật 2FA trong lúc chờ: đăng nhập lại để nhập mã |
| 422 | error_two_factor_code_invalid | Mã sai, hoặc mã đã dùng (mã 6 số không dùng lại được trong cùng 30 giây; mã khôi phục chỉ dùng một lần) |
| 422 | error_two_factor_setup_expired | (enroll) Mã QR hết hạn: gọi lại enrollinit |
| 429 | error_two_factor_too_many_attempts | Sai quá 5 lần trong 15 phút |
Error Codes
| HTTP | Code | Mô tả |
|---|---|---|
| 401 | error_employee_not_found | Không tìm thấy thông tin nhân viên |
| 401 | error_two_factor_required | Mật khẩu đúng, cần mã xác thực 2 lớp (xem Bước 3). Response có detail.challenge |
| 401 | error_two_factor_enroll_required | Mật khẩu đúng, công ty bắt buộc 2FA (hoặc chủ shop có kết nối sàn) mà tài khoản chưa bật (xem Bước 3). Response có detail.challenge, detail.required_reason |
| 403 | error_company_locked | Công ty đang bị khoá, không đăng nhập được. Response có thêm detail.reason, detail.state (xem HTTP Status Code) |
| 422 | error_email_required | Thiếu thông tin email |
| 422 | error_email_format_invalid | Định dạng email không hợp lệ |
| 422 | error_password_empty | Thiếu mật khẩu |
| 422 | error_account_invalid | Email hoặc mật khẩu không đúng. Email chưa có tài khoản và sai mật khẩu trả cùng mã này (không cho biết email nào đã có tài khoản) |
| 422 | error_company_id_not_found | Thiếu hoặc không tìm thấy ID công ty (chỉ ở bước 2) |
| 422 | error_employee_with_email_not_found | Email không thuộc nhân viên của công ty này (chỉ ở bước 2, chỉ trả khi email và mật khẩu đúng) |
| 500 | error_session_can_not_be_saved | Lỗi tạo phiên làm việc |
Mã lỗi đã bỏ
error_account_not_found không còn được trả về (gộp vào error_account_invalid). Ứng dụng tích hợp nên hiển thị cùng một thông báo cho cả hai mã.
Sử dụng JWT Token
Sau khi đăng nhập thành công, sử dụng JWT token trong header của các request tiếp theo (gửi nguyên giá trị, không có tiền tố Bearer):
Authorization: 0123456789abcdef0123456789abcdefXem thêm User Login Token.