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,42 @@
import { Inject, InternalServerErrorException } from '@nestjs/common';
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
import { DomainException, type LoggerService, ValidationException } from '@modules/shared';
import { USER_REPOSITORY, type IUserRepository } from '../../../domain/repositories/user.repository';
import { GetMfaStatusQuery } from './get-mfa-status.query';
export interface MfaStatusDto {
enabled: boolean;
enabledAt: string | null;
backupCodesRemaining: number;
}
@QueryHandler(GetMfaStatusQuery)
export class GetMfaStatusHandler implements IQueryHandler<GetMfaStatusQuery> {
constructor(
@Inject(USER_REPOSITORY) private readonly userRepo: IUserRepository,
private readonly logger: LoggerService,
) {}
async execute(query: GetMfaStatusQuery): Promise<MfaStatusDto> {
try {
const user = await this.userRepo.findById(query.userId);
if (!user) {
throw new ValidationException('Người dùng không tồn tại');
}
return {
enabled: user.totpEnabled,
enabledAt: user.totpEnabledAt?.toISOString() ?? null,
backupCodesRemaining: user.totpBackupCodes.length,
};
} catch (error) {
if (error instanceof DomainException) throw error;
this.logger.error(
`Failed to get MFA status: ${error instanceof Error ? error.message : error}`,
error instanceof Error ? error.stack : undefined,
this.constructor.name,
);
throw new InternalServerErrorException('Không thể lấy trạng thái MFA');
}
}
}

View File

@@ -0,0 +1,3 @@
export class GetMfaStatusQuery {
constructor(public readonly userId: string) {}
}