feat: password reset flow

This commit is contained in:
Nathnael
2026-07-09 08:50:08 +00:00
parent d1652c1b96
commit e04b513b8f
31 changed files with 1350 additions and 250 deletions

View File

@@ -0,0 +1,48 @@
import {
Body,
Controller,
NotFoundException,
Param,
ParseUUIDPipe,
Post,
} from "@nestjs/common";
import { ApiBearerAuth, ApiOperation, ApiTags } from "@nestjs/swagger";
import { BookingStaff } from "../../common/booking-guards";
import { FREIGHT_PERMS } from "../../seed/freight-permissions.registry";
import { BackofficeResetPasswordDto } from "./dto/forgot-password.dto";
import { CustomerResetService } from "./customer-reset.service";
/**
* Staff-triggered password reset. The customer receives the code and sets their
* own password — staff never see or handle a credential.
*/
@ApiTags("backoffice")
@Controller("backoffice/customers")
@ApiBearerAuth()
export class CustomerResetController {
constructor(private readonly customerResetService: CustomerResetService) {}
@Post(":companyId/reset-password")
@BookingStaff(FREIGHT_PERMS.customers.resetPassword)
@ApiOperation({
summary: "Send a password-reset code to a customer's primary contact",
})
async resetPassword(
@Param("companyId", ParseUUIDPipe) companyId: string,
@Body() dto: BackofficeResetPasswordDto,
) {
const maskedTarget = await this.customerResetService.sendResetToCustomer(
companyId,
dto.channel,
);
if (!maskedTarget) {
throw new NotFoundException(
`No active primary contact with ${dto.channel === "email" ? "an email address" : "a phone number"} for this customer`,
);
}
return { channel: dto.channel, maskedTarget };
}
}