docs: dịch 22 file Markdown còn lại sang tiếng Việt có dấu (TEC-2881)
Some checks failed
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 18s
CI / E2E Tests (push) Has been skipped
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 2m15s
Deploy / Build API Image (push) Failing after 28s
Deploy / Build Web Image (push) Failing after 16s
Deploy / Build AI Services Image (push) Failing after 17s
E2E Tests / Playwright E2E (push) Failing after 31s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 3s
Security Scanning / Trivy Scan — API Image (push) Failing after 1m46s
Security Scanning / Trivy Scan — Web Image (push) Failing after 1m7s
Security Scanning / Trivy Scan — AI Services Image (push) Failing after 53s
Security Scanning / Trivy Filesystem Scan (push) Failing after 35s
Deploy / Deploy to Staging (push) Has been skipped
Deploy / Smoke Test Staging (push) Has been skipped
Deploy / Deploy to Production (push) Has been skipped
Deploy / Smoke Test Production (push) Has been skipped
Security Scanning / Security Gate (push) Failing after 0s
Deploy / Rollback Staging (push) Has been skipped
Deploy / Rollback Production (push) Has been skipped
Some checks failed
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 18s
CI / E2E Tests (push) Has been skipped
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 2m15s
Deploy / Build API Image (push) Failing after 28s
Deploy / Build Web Image (push) Failing after 16s
Deploy / Build AI Services Image (push) Failing after 17s
E2E Tests / Playwright E2E (push) Failing after 31s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 3s
Security Scanning / Trivy Scan — API Image (push) Failing after 1m46s
Security Scanning / Trivy Scan — Web Image (push) Failing after 1m7s
Security Scanning / Trivy Scan — AI Services Image (push) Failing after 53s
Security Scanning / Trivy Filesystem Scan (push) Failing after 35s
Deploy / Deploy to Staging (push) Has been skipped
Deploy / Smoke Test Staging (push) Has been skipped
Deploy / Deploy to Production (push) Has been skipped
Deploy / Smoke Test Production (push) Has been skipped
Security Scanning / Security Gate (push) Failing after 0s
Deploy / Rollback Staging (push) Has been skipped
Deploy / Rollback Production (push) Has been skipped
Hoàn tất đợt cuối của nhiệm vụ chuyển toàn bộ tài liệu sang tiếng Việt. Đã dịch 22 file `.md` còn sót (~9.7k dòng) — gồm RUNBOOK, audits, docs/architecture, docs/load-testing, libs READMEs và các quick references. Giữ nguyên code blocks, đường dẫn, identifier kỹ thuật, URL và biến môi trường. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -1,114 +1,114 @@
|
||||
# GoodGo Platform - Authentication Implementation Checklist
|
||||
# GoodGo Platform - Checklist Triển Khai Authentication
|
||||
|
||||
**Date:** April 12, 2026
|
||||
**Status:** ✅ Complete Analysis
|
||||
**Ngày:** 12 tháng 4, 2026
|
||||
**Trạng thái:** ✅ Phân Tích Hoàn Tất
|
||||
|
||||
---
|
||||
|
||||
## 📋 Authentication System Components
|
||||
## 📋 Các Thành Phần Của Hệ Thống Authentication
|
||||
|
||||
### ✅ 1. Password Hashing
|
||||
- **Algorithm:** bcrypt
|
||||
- **Salt Rounds:** 12 (configurable via `BCRYPT_ROUNDS` env var)
|
||||
- **Min Password:** 8 characters
|
||||
- **Location:** `apps/api/src/modules/auth/domain/value-objects/hashed-password.vo.ts`
|
||||
- **Method Used:** `HashedPassword.fromPlain(password)` → async bcrypt.hash()
|
||||
- **Comparison:** `passwordHash.compare(plainPassword)` → bcrypt.compare()
|
||||
### ✅ 1. Hashing Mật Khẩu
|
||||
- **Thuật toán:** bcrypt
|
||||
- **Salt Rounds:** 12 (có thể cấu hình qua biến env `BCRYPT_ROUNDS`)
|
||||
- **Mật khẩu tối thiểu:** 8 ký tự
|
||||
- **Vị trí:** `apps/api/src/modules/auth/domain/value-objects/hashed-password.vo.ts`
|
||||
- **Method dùng:** `HashedPassword.fromPlain(password)` → async bcrypt.hash()
|
||||
- **So sánh:** `passwordHash.compare(plainPassword)` → bcrypt.compare()
|
||||
|
||||
### ✅ 2. Phone Validation & Normalization
|
||||
### ✅ 2. Validate & Chuẩn Hóa Số Điện Thoại
|
||||
- **File:** `apps/api/src/modules/shared/utils/vietnam-phone.validator.ts`
|
||||
- **Regex:** `/^(?:\+84|84|0)(3[2-9]|5[2689]|7[06-9]|8[1-9]|9[0-9])\d{7}$/`
|
||||
- **Accepted Formats:**
|
||||
- **Định dạng được chấp nhận:**
|
||||
- `0900000001` (local)
|
||||
- `84900000001` (country code, no +)
|
||||
- `+84900000001` (international)
|
||||
- **Normalized Format:** Always `+84XXX...` (country code prefix)
|
||||
- **Carriers:** Mobile only (no landlines)
|
||||
- `84900000001` (mã quốc gia, không +)
|
||||
- `+84900000001` (quốc tế)
|
||||
- **Định dạng chuẩn hóa:** Luôn là `+84XXX...` (prefix mã quốc gia)
|
||||
- **Nhà mạng:** Chỉ di động (không có cố định)
|
||||
- 32-39: Viettel/VinaPhone/MobiFone
|
||||
- 52, 56, 58, 59: Viettel
|
||||
- 70, 76-79: Newer carriers
|
||||
- 70, 76-79: Nhà mạng mới
|
||||
- 81-89: VinaPhone
|
||||
- 90-99: MobiFone
|
||||
|
||||
### ✅ 3. Email Validation & Normalization
|
||||
### ✅ 3. Validate & Chuẩn Hóa Email
|
||||
- **File:** `apps/api/src/modules/auth/domain/value-objects/email.vo.ts`
|
||||
- **Regex:** `/^[^\s@]+@[^\s@]+\.[^\s@]+$/` (basic validation)
|
||||
- **Normalization:** lowercase + trimmed
|
||||
- **Example:** `ADMIN@GOODGO.VN` → stored as `admin@goodgo.vn`
|
||||
- **Regex:** `/^[^\s@]+@[^\s@]+\.[^\s@]+$/` (validate cơ bản)
|
||||
- **Chuẩn hóa:** lowercase + trim
|
||||
- **Ví dụ:** `ADMIN@GOODGO.VN` → lưu thành `admin@goodgo.vn`
|
||||
|
||||
### ✅ 4. PII Encryption & Hashing
|
||||
### ✅ 4. Mã Hóa & Hashing PII
|
||||
- **File:** `apps/api/src/modules/shared/infrastructure/field-encryption.ts`
|
||||
- **Encryption Algorithm:** AES-256-GCM
|
||||
- **Key Size:** 32 bytes (64 hex characters)
|
||||
- **IV:** 12 bytes (random)
|
||||
- **Auth Tag:** 16 bytes
|
||||
- **Storage Format:** `enc:v{version}:{iv}:{authTag}:{ciphertext}` (hex)
|
||||
- **Encrypted Fields:**
|
||||
- `email` → stored encrypted + hash in `emailHash`
|
||||
- `phone` → stored encrypted + hash in `phoneHash`
|
||||
- `kycData` → stored encrypted (no separate hash)
|
||||
- **Hash Function:** HMAC-SHA256 (derived from encryption key via HKDF-SHA256)
|
||||
- **Hash Normalization:** lowercase + trimmed
|
||||
- **Env Vars:**
|
||||
- `FIELD_ENCRYPTION_KEY` (required) - hex string, 64 chars
|
||||
- `FIELD_ENCRYPTION_KEY_VERSION` (optional, default: 1)
|
||||
- **Thuật toán mã hóa:** AES-256-GCM
|
||||
- **Kích thước key:** 32 byte (64 ký tự hex)
|
||||
- **IV:** 12 byte (ngẫu nhiên)
|
||||
- **Auth Tag:** 16 byte
|
||||
- **Định dạng lưu trữ:** `enc:v{version}:{iv}:{authTag}:{ciphertext}` (hex)
|
||||
- **Field được mã hóa:**
|
||||
- `email` → lưu mã hóa + hash trong `emailHash`
|
||||
- `phone` → lưu mã hóa + hash trong `phoneHash`
|
||||
- `kycData` → lưu mã hóa (không có hash riêng)
|
||||
- **Hàm hash:** HMAC-SHA256 (dẫn xuất từ encryption key qua HKDF-SHA256)
|
||||
- **Chuẩn hóa hash:** lowercase + trim
|
||||
- **Biến env:**
|
||||
- `FIELD_ENCRYPTION_KEY` (bắt buộc) - chuỗi hex, 64 ký tự
|
||||
- `FIELD_ENCRYPTION_KEY_VERSION` (tùy chọn, mặc định: 1)
|
||||
- Fallback: `KYC_ENCRYPTION_KEY` / `KYC_ENCRYPTION_KEY_VERSION`
|
||||
|
||||
### ✅ 5. Login Flow
|
||||
### ✅ 5. Luồng Đăng Nhập
|
||||
- **File:** `apps/api/src/modules/auth/infrastructure/strategies/local.strategy.ts`
|
||||
- **Username Field:** `phone` (Vietnamese format)
|
||||
- **Password Field:** `password` (plaintext)
|
||||
- **User Lookup:** By `phoneHash` (unique index)
|
||||
- **Steps:**
|
||||
1. Normalize phone
|
||||
2. Find user by phoneHash
|
||||
3. Check `isActive` = true
|
||||
4. Compare password (bcrypt)
|
||||
5. Check `totpEnabled`
|
||||
6. Issue JWT tokens or MFA challenge
|
||||
- **MFA Response (if enabled):** `challengeId` + 5-minute TTL
|
||||
- **No MFA Response:** `accessToken` + `refreshToken` + expiry
|
||||
- **Trường username:** `phone` (định dạng Việt Nam)
|
||||
- **Trường password:** `password` (plaintext)
|
||||
- **Lookup user:** Theo `phoneHash` (unique index)
|
||||
- **Các bước:**
|
||||
1. Chuẩn hóa số điện thoại
|
||||
2. Tìm user theo phoneHash
|
||||
3. Kiểm tra `isActive` = true
|
||||
4. So sánh password (bcrypt)
|
||||
5. Kiểm tra `totpEnabled`
|
||||
6. Cấp JWT token hoặc MFA challenge
|
||||
- **Phản hồi MFA (nếu bật):** `challengeId` + TTL 5 phút
|
||||
- **Phản hồi không có MFA:** `accessToken` + `refreshToken` + thời hạn
|
||||
|
||||
### ✅ 6. User Roles
|
||||
### ✅ 6. Vai Trò User
|
||||
- **Enum:** `UserRole` (Prisma)
|
||||
- **Values:**
|
||||
- `BUYER` (default) - Can search, inquire, make offers
|
||||
- `SELLER` - Can create listings
|
||||
- `AGENT` - Professional agent with verified profile
|
||||
- `ADMIN` - Full platform access
|
||||
- **Default Role:** `BUYER`
|
||||
- **Admin Role:** Created explicitly with `role: 'ADMIN'`
|
||||
- **Giá trị:**
|
||||
- `BUYER` (mặc định) - Có thể tìm kiếm, hỏi, đặt giá
|
||||
- `SELLER` - Có thể tạo tin đăng
|
||||
- `AGENT` - Agent chuyên nghiệp với hồ sơ đã verify
|
||||
- `ADMIN` - Truy cập đầy đủ platform
|
||||
- **Vai trò mặc định:** `BUYER`
|
||||
- **Vai trò Admin:** Tạo rõ ràng với `role: 'ADMIN'`
|
||||
|
||||
### ✅ 7. MFA (Multi-Factor Authentication)
|
||||
- **TOTP:**
|
||||
- Generator: otplib (RFC 6238)
|
||||
- Period: 30 seconds
|
||||
- Digits: 6
|
||||
- Clock Skew: ±30 seconds
|
||||
- **Backup Codes:**
|
||||
- Count: 10
|
||||
- Length: 8 characters each
|
||||
- Charset: A-Z (no O, I), 2-9 (no 0, 1)
|
||||
- Hashing: HMAC-SHA256 (not bcrypt)
|
||||
- Secret Key: `MFA_BACKUP_CODE_SECRET` or fallback to `JWT_SECRET`
|
||||
- **TOTP Secret Storage:** Encrypted with AES-256-GCM
|
||||
- Period: 30 giây
|
||||
- Digit: 6
|
||||
- Clock Skew: ±30 giây
|
||||
- **Backup Code:**
|
||||
- Số lượng: 10
|
||||
- Độ dài: 8 ký tự mỗi mã
|
||||
- Charset: A-Z (không có O, I), 2-9 (không có 0, 1)
|
||||
- Hashing: HMAC-SHA256 (không phải bcrypt)
|
||||
- Secret Key: `MFA_BACKUP_CODE_SECRET` hoặc fallback về `JWT_SECRET`
|
||||
- **Lưu trữ TOTP Secret:** Mã hóa với AES-256-GCM
|
||||
|
||||
### ✅ 8. User Model Fields (Required for Login)
|
||||
### ✅ 8. Field User Model (Bắt Buộc Cho Đăng Nhập)
|
||||
```typescript
|
||||
User {
|
||||
id: string // CUID
|
||||
phone: string // Normalized: +84XXX...
|
||||
phone: string // Chuẩn hóa: +84XXX...
|
||||
phoneHash: string // HMAC-SHA256 (unique index)
|
||||
email?: string // Lowercase, trimmed (encrypted)
|
||||
email?: string // Lowercase, trim (mã hóa)
|
||||
emailHash?: string // HMAC-SHA256 (unique index)
|
||||
passwordHash?: string // bcrypt hash (nullable for OAuth)
|
||||
passwordHash?: string // hash bcrypt (nullable cho OAuth)
|
||||
fullName: string
|
||||
role: UserRole // BUYER | SELLER | AGENT | ADMIN
|
||||
isActive: boolean // true = can login
|
||||
isActive: boolean // true = có thể đăng nhập
|
||||
kycStatus: KYCStatus // NONE | PENDING | VERIFIED | REJECTED
|
||||
totpEnabled: boolean // MFA enabled
|
||||
totpSecret?: string // Encrypted
|
||||
totpBackupCodes: string[] // HMAC-SHA256 hashed codes
|
||||
totpEnabled: boolean // MFA đã bật
|
||||
totpSecret?: string // Mã hóa
|
||||
totpBackupCodes: string[] // Mã đã hash HMAC-SHA256
|
||||
createdAt: DateTime
|
||||
updatedAt: DateTime
|
||||
}
|
||||
@@ -116,32 +116,32 @@ User {
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Creating Login-Capable Seed Users
|
||||
## 🔐 Tạo Seed User Có Khả Năng Đăng Nhập
|
||||
|
||||
### Requirements Checklist
|
||||
- [ ] Password ≥ 8 characters
|
||||
- [ ] Phone matches Vietnamese regex
|
||||
- [ ] Phone normalized to `+84...` format
|
||||
- [ ] Email matches basic regex `^[^\s@]+@[^\s@]+\.[^\s@]+$`
|
||||
### Checklist Yêu Cầu
|
||||
- [ ] Mật khẩu ≥ 8 ký tự
|
||||
- [ ] Số điện thoại khớp regex Việt Nam
|
||||
- [ ] Số điện thoại chuẩn hóa thành định dạng `+84...`
|
||||
- [ ] Email khớp regex cơ bản `^[^\s@]+@[^\s@]+\.[^\s@]+$`
|
||||
- [ ] Email lowercased
|
||||
- [ ] Password hashed with bcrypt (≥12 rounds)
|
||||
- [ ] `phoneHash` computed (HMAC-SHA256)
|
||||
- [ ] `emailHash` computed (HMAC-SHA256)
|
||||
- [ ] Mật khẩu hash với bcrypt (≥12 rounds)
|
||||
- [ ] `phoneHash` được tính (HMAC-SHA256)
|
||||
- [ ] `emailHash` được tính (HMAC-SHA256)
|
||||
- [ ] `isActive: true`
|
||||
- [ ] `totpEnabled: false` (for seed users)
|
||||
- [ ] `totpEnabled: false` (cho seed user)
|
||||
- [ ] `totpBackupCodes: []`
|
||||
|
||||
### Implementation Steps
|
||||
### Các Bước Triển Khai
|
||||
|
||||
**Step 1: Normalize Phone**
|
||||
**Bước 1: Chuẩn Hóa Số Điện Thoại**
|
||||
```typescript
|
||||
const phone = '0900000001';
|
||||
const normalized = `+84${phone.slice(1)}`; // '+84900000001'
|
||||
```
|
||||
|
||||
**Step 2: Derive HMAC Key**
|
||||
**Bước 2: Dẫn Xuất HMAC Key**
|
||||
```typescript
|
||||
const encryptionKey = process.env['FIELD_ENCRYPTION_KEY']; // hex string
|
||||
const encryptionKey = process.env['FIELD_ENCRYPTION_KEY']; // chuỗi hex
|
||||
const hmacKey = crypto.hkdfSync(
|
||||
'sha256',
|
||||
Buffer.from(encryptionKey, 'hex'),
|
||||
@@ -151,7 +151,7 @@ const hmacKey = crypto.hkdfSync(
|
||||
);
|
||||
```
|
||||
|
||||
**Step 3: Compute Hashes**
|
||||
**Bước 3: Tính Hash**
|
||||
```typescript
|
||||
const phoneHash = crypto
|
||||
.createHmac('sha256', hmacKey)
|
||||
@@ -164,12 +164,12 @@ const emailHash = crypto
|
||||
.digest('hex');
|
||||
```
|
||||
|
||||
**Step 4: Hash Password**
|
||||
**Bước 4: Hash Mật Khẩu**
|
||||
```typescript
|
||||
const passwordHash = await bcrypt.hash('AdminPassword123', 12);
|
||||
```
|
||||
|
||||
**Step 5: Create User**
|
||||
**Bước 5: Tạo User**
|
||||
```typescript
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
@@ -191,15 +191,15 @@ await prisma.user.create({
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing Login
|
||||
## 🧪 Test Đăng Nhập
|
||||
|
||||
### Prerequisites
|
||||
- User exists in database
|
||||
- `passwordHash` is set (not null)
|
||||
### Yêu Cầu Trước
|
||||
- User tồn tại trong database
|
||||
- `passwordHash` được set (không null)
|
||||
- `isActive: true`
|
||||
- No MFA enabled (or have MFA code ready)
|
||||
- Không bật MFA (hoặc có sẵn mã MFA)
|
||||
|
||||
### Test Request
|
||||
### Request Test
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
@@ -209,7 +209,7 @@ curl -X POST http://localhost:3000/auth/login \
|
||||
}'
|
||||
```
|
||||
|
||||
### Expected Response (Success)
|
||||
### Phản Hồi Mong Đợi (Thành Công)
|
||||
```json
|
||||
{
|
||||
"requiresMfa": false,
|
||||
@@ -221,127 +221,127 @@ curl -X POST http://localhost:3000/auth/login \
|
||||
}
|
||||
```
|
||||
|
||||
### Error Cases
|
||||
- **Invalid phone format:** "Số điện thoại không hợp lệ"
|
||||
- **User not found:** "Số điện thoại hoặc mật khẩu không đúng"
|
||||
- **User inactive:** "Tài khoản đã bị vô hiệu hóa"
|
||||
- **Wrong password:** "Số điện thoại hoặc mật khẩu không đúng"
|
||||
- **MFA required:** `{ "requiresMfa": true, "challengeId": "..." }`
|
||||
### Các Trường Hợp Lỗi
|
||||
- **Định dạng số điện thoại không hợp lệ:** "Số điện thoại không hợp lệ"
|
||||
- **Không tìm thấy user:** "Số điện thoại hoặc mật khẩu không đúng"
|
||||
- **User không active:** "Tài khoản đã bị vô hiệu hóa"
|
||||
- **Sai mật khẩu:** "Số điện thoại hoặc mật khẩu không đúng"
|
||||
- **Yêu cầu MFA:** `{ "requiresMfa": true, "challengeId": "..." }`
|
||||
|
||||
---
|
||||
|
||||
## 📁 Key Files Reference
|
||||
## 📁 Tham Khảo Các File Chính
|
||||
|
||||
| File | Purpose | Key Functions |
|
||||
| File | Mục đích | Hàm chính |
|
||||
|------|---------|---------------|
|
||||
| `hashed-password.vo.ts` | Password hashing | `fromPlain()`, `compare()` |
|
||||
| `phone.vo.ts` | Phone validation | `create()` |
|
||||
| `email.vo.ts` | Email validation | `create()` |
|
||||
| `vietnam-phone.validator.ts` | Phone regex/normalize | `isValidVietnamPhone()`, `normalizeVietnamPhone()` |
|
||||
| `field-encryption.ts` | PII encryption/hashing | `encryptField()`, `decryptField()`, `computeHash()` |
|
||||
| `local.strategy.ts` | Login flow | `validate()` |
|
||||
| `mfa.service.ts` | TOTP/backup codes | `generateSetup()`, `verifyTotp()`, `generateBackupCodes()` |
|
||||
| `user.entity.ts` | User domain model | `createNew()` |
|
||||
| `prisma-user.repository.ts` | User persistence | `findByPhone()`, `save()` |
|
||||
| `encrypt-pii-fields.ts` | Backfill encryption | Batch encryption migration |
|
||||
| `schema.prisma` | Database schema | User model, enums |
|
||||
| `seed.ts` | Seed data | Current seeds (no passwords) |
|
||||
| `hashed-password.vo.ts` | Hash mật khẩu | `fromPlain()`, `compare()` |
|
||||
| `phone.vo.ts` | Validate số điện thoại | `create()` |
|
||||
| `email.vo.ts` | Validate email | `create()` |
|
||||
| `vietnam-phone.validator.ts` | Regex/chuẩn hóa số điện thoại | `isValidVietnamPhone()`, `normalizeVietnamPhone()` |
|
||||
| `field-encryption.ts` | Mã hóa/hash PII | `encryptField()`, `decryptField()`, `computeHash()` |
|
||||
| `local.strategy.ts` | Luồng đăng nhập | `validate()` |
|
||||
| `mfa.service.ts` | TOTP/backup code | `generateSetup()`, `verifyTotp()`, `generateBackupCodes()` |
|
||||
| `user.entity.ts` | Domain model User | `createNew()` |
|
||||
| `prisma-user.repository.ts` | Persist user | `findByPhone()`, `save()` |
|
||||
| `encrypt-pii-fields.ts` | Backfill mã hóa | Migration mã hóa hàng loạt |
|
||||
| `schema.prisma` | Schema database | Model User, enum |
|
||||
| `seed.ts` | Dữ liệu seed | Seed hiện tại (không có mật khẩu) |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Deployment Checklist
|
||||
## 🚀 Checklist Deployment
|
||||
|
||||
### Environment Variables Required
|
||||
- [ ] `BCRYPT_ROUNDS` (optional, default: 12)
|
||||
- [ ] `FIELD_ENCRYPTION_KEY` (required for PII, hex string 64 chars)
|
||||
- [ ] `FIELD_ENCRYPTION_KEY_VERSION` (optional, default: 1)
|
||||
- [ ] `MFA_BACKUP_CODE_SECRET` (optional, fallback to JWT_SECRET)
|
||||
- [ ] `JWT_SECRET` (required for tokens)
|
||||
### Biến Môi Trường Bắt Buộc
|
||||
- [ ] `BCRYPT_ROUNDS` (tùy chọn, mặc định: 12)
|
||||
- [ ] `FIELD_ENCRYPTION_KEY` (bắt buộc cho PII, chuỗi hex 64 ký tự)
|
||||
- [ ] `FIELD_ENCRYPTION_KEY_VERSION` (tùy chọn, mặc định: 1)
|
||||
- [ ] `MFA_BACKUP_CODE_SECRET` (tùy chọn, fallback về JWT_SECRET)
|
||||
- [ ] `JWT_SECRET` (bắt buộc cho token)
|
||||
|
||||
### Database Setup
|
||||
- [ ] Run migrations (including `add_mfa_totp_support`)
|
||||
- [ ] Seed users with passwords (use provided script)
|
||||
- [ ] Test login functionality
|
||||
- [ ] Verify PII encryption working
|
||||
### Cài Đặt Database
|
||||
- [ ] Chạy migration (bao gồm `add_mfa_totp_support`)
|
||||
- [ ] Seed user với mật khẩu (dùng script đã cung cấp)
|
||||
- [ ] Test chức năng đăng nhập
|
||||
- [ ] Verify mã hóa PII hoạt động
|
||||
|
||||
### Testing
|
||||
- [ ] Test login with various phone formats (0900..., 84900..., +84900...)
|
||||
- [ ] Test invalid phone numbers (rejected)
|
||||
- [ ] Test password validation (min 8 chars)
|
||||
- [ ] Test email validation
|
||||
- [ ] Test MFA setup and verification
|
||||
- [ ] Test backup code generation/usage
|
||||
- [ ] Verify hashes computed correctly
|
||||
- [ ] Test đăng nhập với nhiều định dạng số điện thoại (0900..., 84900..., +84900...)
|
||||
- [ ] Test số điện thoại không hợp lệ (bị từ chối)
|
||||
- [ ] Test validate mật khẩu (tối thiểu 8 ký tự)
|
||||
- [ ] Test validate email
|
||||
- [ ] Test setup và verify MFA
|
||||
- [ ] Test sinh/dùng backup code
|
||||
- [ ] Verify hash được tính đúng
|
||||
|
||||
---
|
||||
|
||||
## 📝 Current Seed Data Status
|
||||
## 📝 Trạng Thái Dữ Liệu Seed Hiện Tại
|
||||
|
||||
### Existing Seed (prisma/seed.ts)
|
||||
**Status:** ❌ **NOT login-capable** (no passwords)
|
||||
### Seed Hiện Có (prisma/seed.ts)
|
||||
**Trạng thái:** ❌ **KHÔNG có khả năng đăng nhập** (không có mật khẩu)
|
||||
|
||||
```typescript
|
||||
// Current seed - users created without passwords
|
||||
// Seed hiện tại - user được tạo không có mật khẩu
|
||||
const admin = await prisma.user.upsert({
|
||||
where: { id: 'seed-user-admin' },
|
||||
create: {
|
||||
id: 'seed-user-admin',
|
||||
phone: '0900000001', // NOT normalized/hashed
|
||||
phone: '0900000001', // CHƯA chuẩn hóa/hash
|
||||
email: 'admin@goodgo.vn',
|
||||
fullName: 'Admin GoodGo',
|
||||
role: UserRole.ADMIN,
|
||||
// passwordHash: null ← Cannot login!
|
||||
// passwordHash: null ← Không thể đăng nhập!
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Recommended Enhancement
|
||||
Use `SEED_GENERATION_SCRIPT.ts` to create users with full auth capability.
|
||||
### Cải Tiến Khuyến Nghị
|
||||
Dùng `SEED_GENERATION_SCRIPT.ts` để tạo user với khả năng auth đầy đủ.
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Troubleshooting
|
||||
|
||||
### User Can't Login
|
||||
1. Verify `passwordHash` is NOT null: `SELECT id, passwordHash FROM "User" WHERE id = 'user-id';`
|
||||
2. Check `isActive = true`
|
||||
3. Verify phone is normalized to `+84...` format
|
||||
4. Test phone normalization function directly
|
||||
### User Không Đăng Nhập Được
|
||||
1. Verify `passwordHash` KHÔNG null: `SELECT id, passwordHash FROM "User" WHERE id = 'user-id';`
|
||||
2. Kiểm tra `isActive = true`
|
||||
3. Verify số điện thoại chuẩn hóa thành định dạng `+84...`
|
||||
4. Test trực tiếp hàm chuẩn hóa số điện thoại
|
||||
|
||||
### Phone Hash Mismatch
|
||||
1. Verify `FIELD_ENCRYPTION_KEY` is same across deployments
|
||||
2. Check hash computation: `HMAC-SHA256(lowercased_phone, hmacKey)`
|
||||
3. HKDF derivation must use exact string: `"goodgo-field-hash"`
|
||||
### Phone Hash Không Khớp
|
||||
1. Verify `FIELD_ENCRYPTION_KEY` giống nhau giữa các deployment
|
||||
2. Kiểm tra cách tính hash: `HMAC-SHA256(lowercased_phone, hmacKey)`
|
||||
3. Dẫn xuất HKDF phải dùng đúng chuỗi: `"goodgo-field-hash"`
|
||||
|
||||
### MFA Not Working
|
||||
1. Verify `MFA_BACKUP_CODE_SECRET` is set
|
||||
2. Check TOTP secret is encrypted properly
|
||||
3. Test clock skew (±30s tolerance)
|
||||
### MFA Không Hoạt Động
|
||||
1. Verify `MFA_BACKUP_CODE_SECRET` đã được set
|
||||
2. Kiểm tra TOTP secret được mã hóa đúng cách
|
||||
3. Test clock skew (dung sai ±30s)
|
||||
|
||||
### Encryption/Decryption Issues
|
||||
1. Verify key is exactly 32 bytes (64 hex chars)
|
||||
2. Check IV length (12 bytes)
|
||||
3. Verify auth tag (16 bytes)
|
||||
4. Ensure `enc:` prefix detection working
|
||||
### Vấn Đề Mã Hóa/Giải Mã
|
||||
1. Verify key đúng 32 byte (64 ký tự hex)
|
||||
2. Kiểm tra độ dài IV (12 byte)
|
||||
3. Verify auth tag (16 byte)
|
||||
4. Đảm bảo phát hiện prefix `enc:` hoạt động
|
||||
|
||||
---
|
||||
|
||||
## 📚 Additional Resources
|
||||
## 📚 Tài Nguyên Bổ Sung
|
||||
|
||||
### External Documentation
|
||||
### Tài Liệu Bên Ngoài
|
||||
- **bcrypt:** https://github.com/kelektiv/node.bcrypt.js
|
||||
- **otplib:** https://github.com/yeojz/otplib
|
||||
- **Prisma:** https://www.prisma.io/docs
|
||||
- **NestJS:** https://docs.nestjs.com
|
||||
|
||||
### Related Files
|
||||
- Registration flow: `register-user.handler.ts`
|
||||
- Token generation: `token.service.ts`
|
||||
### File Liên Quan
|
||||
- Luồng đăng ký: `register-user.handler.ts`
|
||||
- Sinh token: `token.service.ts`
|
||||
- JWT strategy: `jwt.strategy.ts`
|
||||
- Refresh token: `refresh-token.handler.ts`
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** April 12, 2026
|
||||
**Cập nhật lần cuối:** 12 tháng 4, 2026
|
||||
**Platform:** GoodGo Real Estate Platform
|
||||
**Status:** ✅ Production-Ready
|
||||
**Trạng thái:** ✅ Sẵn sàng Production
|
||||
|
||||
Reference in New Issue
Block a user