feat: add MFA/TOTP auth, PII encryption, agents/leads/inquiries modules, and comprehensive tests

- Add TOTP-based MFA with setup, verify, disable, backup codes, and challenge flow
- Add PII field encryption middleware with AES-256-GCM and deterministic search hashes
- Add agents, inquiries, and leads domain modules with entities, events, value objects
- Add web dashboard pages for inquiries and leads with detail dialogs
- Add 30+ component tests (valuation, charts, listings, search, providers, UI)
- Add Prisma migrations for encryption hash columns and MFA TOTP support
- Fix all ESLint errors (unused imports, duplicate imports, lint auto-fixes)
- Update dependencies and lock file
- Clean up obsolete exploration/QA docs, add audit documentation

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-11 23:43:20 +07:00
parent 9e2bf9a4b5
commit 1fbe2f4e73
131 changed files with 11436 additions and 2595 deletions

View File

@@ -33,8 +33,10 @@ enum KYCStatus {
model User {
id String @id @default(cuid())
email String? @unique
phone String @unique
email String?
emailHash String? @unique
phone String
phoneHash String? @unique
passwordHash String?
fullName String
avatarUrl String?
@@ -47,6 +49,12 @@ model User {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// MFA fields
totpSecret String? // Encrypted TOTP secret
totpEnabled Boolean @default(false)
totpBackupCodes String[] // Bcrypt-hashed backup codes
totpEnabledAt DateTime?
agent Agent?
listings Listing[]
savedSearches SavedSearch[]
@@ -57,6 +65,7 @@ model User {
refreshTokens RefreshToken[]
oauthAccounts OAuthAccount[]
buyerTransactions Transaction[] @relation("BuyerTransactions")
mfaChallenges MfaChallenge[]
@@index([role])
@@index([kycStatus])
@@ -69,6 +78,21 @@ model User {
@@index([kycStatus, createdAt])
}
model MfaChallenge {
id String @id @default(cuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
type String // "totp" | "backup_code"
attemptCount Int @default(0)
maxAttempts Int @default(5)
isVerified Boolean @default(false)
expiresAt DateTime
createdAt DateTime @default(now())
@@index([userId, expiresAt])
@@index([expiresAt])
}
enum OAuthProvider {
GOOGLE
ZALO
@@ -355,7 +379,9 @@ model Lead {
agent Agent @relation(fields: [agentId], references: [id], onDelete: Cascade)
name String
phone String
phoneHash String?
email String?
emailHash String?
source String
score Float?
notes Json?
@@ -365,6 +391,8 @@ model Lead {
@@index([agentId])
@@index([status])
@@index([phoneHash])
@@index([emailHash])
}
// =============================================================================