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

@@ -14,6 +14,7 @@ import { Throttle } from '@nestjs/throttler';
import { type Request, type Response } from 'express';
import { EndpointRateLimit, EndpointRateLimitGuard, UnauthorizedException } from '@modules/shared';
import { LoginUserCommand } from '../../application/commands/login-user/login-user.command';
import { type LoginResult } from '../../application/commands/login-user/login-user.handler';
import { RefreshTokenCommand } from '../../application/commands/refresh-token/refresh-token.command';
import { RegisterUserCommand } from '../../application/commands/register-user/register-user.command';
import { VerifyKycCommand } from '../../application/commands/verify-kyc/verify-kyc.command';
@@ -22,6 +23,7 @@ import { GetAgentByUserIdQuery } from '../../application/queries/get-agent-by-us
import { type UserProfileDto } from '../../application/queries/get-profile/get-profile.handler';
import { GetProfileQuery } from '../../application/queries/get-profile/get-profile.query';
import { type TokenService, type JwtPayload, type TokenPair } from '../../infrastructure/services/token.service';
import { type LocalStrategyResult } from '../../infrastructure/strategies/local.strategy';
import { CurrentUser } from '../decorators/current-user.decorator';
import { Roles } from '../decorators/roles.decorator';
import { LoginDto } from '../dto/login.dto';
@@ -107,20 +109,29 @@ export class AuthController {
@Post('login')
@ApiOperation({ summary: 'Login with phone and password' })
@ApiBody({ type: LoginDto })
@ApiResponse({ status: 201, description: 'Login successful, auth cookies set' })
@ApiResponse({ status: 201, description: 'Login successful, auth cookies set (or MFA challenge returned)' })
@ApiResponse({ status: 401, description: 'Invalid credentials' })
async login(
@CurrentUser() user: { id: string; phone: string; role: string },
@CurrentUser() user: LocalStrategyResult,
@Res({ passthrough: true }) res: Response,
): Promise<{ message: string; accessToken: string; refreshToken: string }> {
const tokens: TokenPair = await this.commandBus.execute(
new LoginUserCommand(user.id, user.phone, user.role),
): Promise<{ message: string; accessToken?: string; refreshToken?: string; requiresMfa?: boolean; challengeId?: string }> {
const result: LoginResult = await this.commandBus.execute(
new LoginUserCommand(user.id, user.phone, user.role, user.isMfaRequired),
);
setAuthCookies(res, tokens);
if (result.requiresMfa) {
return {
message: 'Yêu cầu xác thực MFA',
requiresMfa: true,
challengeId: result.challengeId,
};
}
setAuthCookies(res, result.tokens!);
return {
message: 'Đăng nhập thành công',
accessToken: tokens.accessToken,
refreshToken: tokens.refreshToken,
accessToken: result.tokens!.accessToken,
refreshToken: result.tokens!.refreshToken,
};
}