import { Inject, Injectable, Logger } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { Strategy } from 'passport-local'; import { DomainException, normalizeVietnamPhone, UnauthorizedException } from '@modules/shared'; import { USER_REPOSITORY, type IUserRepository } from '../../domain/repositories/user.repository'; @Injectable() export class LocalStrategy extends PassportStrategy(Strategy) { private readonly logger = new Logger(LocalStrategy.name); constructor( @Inject(USER_REPOSITORY) private readonly userRepo: IUserRepository, ) { super({ usernameField: 'phone', passwordField: 'password' }); } async validate(phone: string, password: string): Promise<{ id: string; phone: string; role: string }> { try { if (!phone || !password) { throw new UnauthorizedException('Số điện thoại hoặc mật khẩu không đúng'); } const normalizedPhone = normalizeVietnamPhone(phone); if (!normalizedPhone) { throw new UnauthorizedException('Số điện thoại không hợp lệ'); } const user = await this.userRepo.findByPhone(normalizedPhone); if (!user || !user.passwordHash) { throw new UnauthorizedException('Số điện thoại hoặc mật khẩu không đúng'); } if (!user.isActive) { throw new UnauthorizedException('Tài khoản đã bị vô hiệu hóa'); } const isValid = await user.passwordHash.compare(password); if (!isValid) { throw new UnauthorizedException('Số điện thoại hoặc mật khẩu không đúng'); } return { id: user.id, phone: user.phone.value, role: user.role }; } catch (error) { if (error instanceof DomainException) throw error; this.logger.error( `Authentication failed: ${error instanceof Error ? error.message : error}`, error instanceof Error ? error.stack : undefined, ); throw new UnauthorizedException('Số điện thoại hoặc mật khẩu không đúng'); } } }