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

@@ -0,0 +1,6 @@
export class DisableMfaCommand {
constructor(
public readonly userId: string,
public readonly totpCode: string,
) {}
}

View File

@@ -0,0 +1,47 @@
import { Inject, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
import { DomainException, type LoggerService, UnauthorizedException, ValidationException } from '@modules/shared';
import { USER_REPOSITORY, type IUserRepository } from '../../../domain/repositories/user.repository';
import { type MfaService } from '../../../infrastructure/services/mfa.service';
import { DisableMfaCommand } from './disable-mfa.command';
@CommandHandler(DisableMfaCommand)
export class DisableMfaHandler implements ICommandHandler<DisableMfaCommand> {
constructor(
@Inject(USER_REPOSITORY) private readonly userRepo: IUserRepository,
private readonly mfaService: MfaService,
private readonly logger: LoggerService,
) {}
async execute(command: DisableMfaCommand): Promise<{ message: string }> {
try {
const user = await this.userRepo.findById(command.userId);
if (!user) {
throw new ValidationException('Người dùng không tồn tại');
}
if (!user.totpEnabled || !user.totpSecret) {
throw new ValidationException('MFA chưa được bật');
}
// Require current TOTP code to disable MFA
const isValid = await this.mfaService.verifyTotp(command.totpCode, user.totpSecret);
if (!isValid) {
throw new UnauthorizedException('Mã TOTP không hợp lệ');
}
// Disable MFA
await this.userRepo.updateMfaDisabled(command.userId);
return { message: 'MFA đã được tắt thành công' };
} catch (error) {
if (error instanceof DomainException) throw error;
this.logger.error(
`Failed to disable MFA: ${error instanceof Error ? error.message : error}`,
error instanceof Error ? error.stack : undefined,
this.constructor.name,
);
throw new InternalServerErrorException('Không thể tắt MFA');
}
}
}