Files
goodgo-platform/docs/guides/AUTH_IMPLEMENTATION_CHECKLIST.md
Ho Ngoc Hai d8b409a9ab
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
docs: dịch 22 file Markdown còn lại sang tiếng Việt có dấu (TEC-2881)
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>
2026-04-19 03:26:14 +07:00

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ành admin@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 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. 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:
    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. 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 đă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 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. 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: true
  • totpEnabled: 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

  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 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 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)

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

📚 Tài Nguyên Bổ Sung

Tài Liệu Bên Ngoài

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