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>
12 KiB
12 KiB
GoodGo Platform - Checklist Triển Khai Authentication
Ngày: 12 tháng 4, 2026 Trạng thái: ✅ Phân Tích Hoàn Tất
📋 Các Thành Phần Của Hệ Thống Authentication
✅ 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. 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}$/ - Định dạng được chấp nhận:
0900000001(local)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: Nhà mạng mới
- 81-89: VinaPhone
- 90-99: MobiFone
✅ 3. Validate & Chuẩn Hóa Email
- File:
apps/api/src/modules/auth/domain/value-objects/email.vo.ts - Regex:
/^[^\s@]+@[^\s@]+\.[^\s@]+$/(validate cơ bản) - Chuẩn hóa: lowercase + trim
- Ví dụ:
ADMIN@GOODGO.VN→ lưu thànhadmin@goodgo.vn
✅ 4. Mã Hóa & Hashing PII
- File:
apps/api/src/modules/shared/infrastructure/field-encryption.ts - 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 trongemailHashphone→ lưu mã hóa + hash trongphoneHashkycData→ 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. Luồng Đăng Nhập
- File:
apps/api/src/modules/auth/infrastructure/strategies/local.strategy.ts - 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:
- Chuẩn hóa số điện thoại
- Tìm user theo phoneHash
- Kiểm tra
isActive= true - So sánh password (bcrypt)
- Kiểm tra
totpEnabled - 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. Vai Trò User
- Enum:
UserRole(Prisma) - Giá trị:
BUYER(mặc định) - Có thể tìm kiếm, hỏi, đặt giáSELLER- Có thể tạo tin đăngAGENT- Agent chuyên nghiệp với hồ sơ đã verifyADMIN- 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 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_SECREThoặc fallback vềJWT_SECRET
- Lưu trữ TOTP Secret: Mã hóa với AES-256-GCM
✅ 8. Field User Model (Bắt Buộc Cho Đăng Nhập)
User {
id: string // CUID
phone: string // Chuẩn hóa: +84XXX...
phoneHash: string // HMAC-SHA256 (unique index)
email?: string // Lowercase, trim (mã hóa)
emailHash?: string // HMAC-SHA256 (unique index)
passwordHash?: string // hash bcrypt (nullable cho OAuth)
fullName: string
role: UserRole // BUYER | SELLER | AGENT | ADMIN
isActive: boolean // true = có thể đăng nhập
kycStatus: KYCStatus // NONE | PENDING | VERIFIED | REJECTED
totpEnabled: boolean // MFA đã bật
totpSecret?: string // Mã hóa
totpBackupCodes: string[] // Mã đã hash HMAC-SHA256
createdAt: DateTime
updatedAt: DateTime
}
🔐 Tạo Seed User Có Khả Năng Đăng Nhập
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
- Mật khẩu hash với bcrypt (≥12 rounds)
phoneHashđược tính (HMAC-SHA256)emailHashđược tính (HMAC-SHA256)isActive: truetotpEnabled: false(cho seed user)totpBackupCodes: []
Các Bước Triển Khai
Bước 1: Chuẩn Hóa Số Điện Thoại
const phone = '0900000001';
const normalized = `+84${phone.slice(1)}`; // '+84900000001'
Bước 2: Dẫn Xuất HMAC Key
const encryptionKey = process.env['FIELD_ENCRYPTION_KEY']; // chuỗi hex
const hmacKey = crypto.hkdfSync(
'sha256',
Buffer.from(encryptionKey, 'hex'),
Buffer.alloc(0),
Buffer.from('goodgo-field-hash', 'utf8'),
32,
);
Bước 3: Tính Hash
const phoneHash = crypto
.createHmac('sha256', hmacKey)
.update(normalized.toLowerCase())
.digest('hex');
const emailHash = crypto
.createHmac('sha256', hmacKey)
.update(email.toLowerCase())
.digest('hex');
Bước 4: Hash Mật Khẩu
const passwordHash = await bcrypt.hash('AdminPassword123', 12);
Bước 5: Tạo User
await prisma.user.create({
data: {
id: 'admin-seed-001',
phone: normalized, // +84900000001
phoneHash,
email,
emailHash,
passwordHash,
fullName: 'Admin GoodGo',
role: 'ADMIN',
kycStatus: 'VERIFIED',
isActive: true,
totpEnabled: false,
totpBackupCodes: [],
},
});
🧪 Test Đăng Nhập
Yêu Cầu Trước
- User tồn tại trong database
passwordHashđược set (không null)isActive: true- Không bật MFA (hoặc có sẵn mã MFA)
Request Test
curl -X POST http://localhost:3000/auth/login \
-H "Content-Type: application/json" \
-d '{
"phone": "0900000001",
"password": "AdminPassword123"
}'
Phản Hồi Mong Đợi (Thành Công)
{
"requiresMfa": false,
"tokens": {
"accessToken": "eyJhbGc...",
"refreshToken": "eyJhbGc...",
"expiresIn": 3600
}
}
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": "..." }
📁 Tham Khảo Các File Chính
| File | Mục đích | Hàm chính |
|---|---|---|
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) |
🚀 Checklist Deployment
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)
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 đă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
📝 Trạng Thái Dữ Liệu Seed Hiện Tại
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)
// 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', // CHƯA chuẩn hóa/hash
email: 'admin@goodgo.vn',
fullName: 'Admin GoodGo',
role: UserRole.ADMIN,
// passwordHash: null ← Không thể đăng nhập!
},
});
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 Không Đăng Nhập Được
- Verify
passwordHashKHÔNG null:SELECT id, passwordHash FROM "User" WHERE id = 'user-id'; - Kiểm tra
isActive = true - Verify số điện thoại chuẩn hóa thành định dạng
+84... - Test trực tiếp hàm chuẩn hóa số điện thoại
Phone Hash Không Khớp
- Verify
FIELD_ENCRYPTION_KEYgiống nhau giữa các deployment - Kiểm tra cách tính hash:
HMAC-SHA256(lowercased_phone, hmacKey) - Dẫn xuất HKDF phải dùng đúng chuỗi:
"goodgo-field-hash"
MFA Không Hoạt Động
- Verify
MFA_BACKUP_CODE_SECRETđã được set - Kiểm tra TOTP secret được mã hóa đúng cách
- Test clock skew (dung sai ±30s)
Vấn Đề Mã Hóa/Giải Mã
- Verify key đúng 32 byte (64 ký tự hex)
- Kiểm tra độ dài IV (12 byte)
- Verify auth tag (16 byte)
- Đảm bảo phát hiện prefix
enc:hoạt động
📚 Tài Nguyên Bổ Sung
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
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
Cập nhật lần cuối: 12 tháng 4, 2026 Platform: GoodGo Real Estate Platform Trạng thái: ✅ Sẵn sàng Production