import { Injectable, Logger } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; import { ExternalProfile } from "../companies/entities/external-profile.entity"; import { ResetChannel } from "./dto/forgot-password.dto"; import { ForgotPasswordService } from "./forgot-password.service"; @Injectable() export class CustomerResetService { private readonly logger = new Logger(CustomerResetService.name); constructor( @InjectRepository(ExternalProfile) private readonly externalProfileRepository: Repository, private readonly forgotPasswordService: ForgotPasswordService, ) {} /** * Send a reset code to the company's primary contact. Returns the masked * destination, or null when there is no eligible account for that channel. * * Unlike the public flow this reports failure honestly — the caller is an * authenticated staff member, so there is nothing to enumerate. */ async sendResetToCustomer( companyId: string, channel: ResetChannel, ): Promise { const profile = await this.externalProfileRepository.findOne({ where: { companyId, isPrimaryContact: true }, }); if (!profile) { this.logger.warn(`Company ${companyId} has no primary contact profile`); return null; } // Resolve through the same active-account gate the public flow uses, so a // suspended customer cannot be reactivated by a staff-triggered reset. const user = await this.forgotPasswordService.resolveActiveUserById( profile.userId, ); if (!user) { this.logger.warn( `Primary contact ${profile.userId} of company ${companyId} is not an active account`, ); return null; } const target = await this.forgotPasswordService.requestReset(user, channel); if (!target) return null; this.logger.log( `Staff-triggered ${channel} reset sent to user ${user.id} (company ${companyId})`, ); return this.forgotPasswordService.maskTarget(target); } }