mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 00:10:57 +00:00
feat: password reset flow
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import { Body, Controller, Logger, Post } from "@nestjs/common";
|
||||
import { ApiOperation, ApiTags } from "@nestjs/swagger";
|
||||
import { Public } from "@edr/api-common";
|
||||
|
||||
import {
|
||||
ForgotPasswordRequestDto,
|
||||
ForgotPasswordVerifyDto,
|
||||
} from "./dto/forgot-password.dto";
|
||||
import { ForgotPasswordService, ResetTicket } from "./forgot-password.service";
|
||||
|
||||
/**
|
||||
* Freight-owned reset flow. IAM ships a `forgot-password` route, but it only
|
||||
* ever SMSes a magic link (no email channel, and it needs `FE_BASE_URL`, which
|
||||
* this API does not set). These routes drive freight's own email-or-phone OTP
|
||||
* service instead, then hand back a ticket for IAM's public `set-password`.
|
||||
*/
|
||||
@ApiTags("auth")
|
||||
@Controller("auth")
|
||||
@Public()
|
||||
export class ForgotPasswordController {
|
||||
private readonly logger = new Logger(ForgotPasswordController.name);
|
||||
|
||||
constructor(private readonly forgotPasswordService: ForgotPasswordService) {}
|
||||
|
||||
@Post("forgot-password/request")
|
||||
@ApiOperation({
|
||||
summary: "Send a password-reset code over email or SMS",
|
||||
description:
|
||||
"Always reports success. An unknown, inactive, or channel-less account is " +
|
||||
"indistinguishable from a real one, so this cannot be used to enumerate accounts.",
|
||||
})
|
||||
async request(@Body() dto: ForgotPasswordRequestDto): Promise<{ success: true }> {
|
||||
const user = await this.forgotPasswordService.resolveActiveUser(dto.identifier);
|
||||
|
||||
if (user) {
|
||||
try {
|
||||
await this.forgotPasswordService.requestReset(user, dto.channel);
|
||||
} catch (error) {
|
||||
// A delivery failure must not change the response shape either — log it
|
||||
// and let the caller sit on the OTP screen.
|
||||
this.logger.error(
|
||||
`Reset code delivery failed for user ${user.id}: ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`,
|
||||
error instanceof Error ? error.stack : undefined,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.logger.log("Reset requested for an unknown or inactive account");
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
@Post("forgot-password/verify")
|
||||
@ApiOperation({
|
||||
summary: "Exchange a valid reset code for a single-use set-password ticket",
|
||||
description:
|
||||
"The returned { userId, verificationCode } is the body for PATCH /api/auth/set-password, " +
|
||||
"alongside the same identifier and the new password.",
|
||||
})
|
||||
verify(@Body() dto: ForgotPasswordVerifyDto): Promise<ResetTicket> {
|
||||
return this.forgotPasswordService.verifyAndMintTicket(
|
||||
dto.identifier,
|
||||
dto.channel,
|
||||
dto.otp,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user