add : forget password

This commit is contained in:
2026-02-02 16:12:17 +03:30
parent b3f9457c78
commit a46dfdea93
8 changed files with 167 additions and 33 deletions
+1
View File
@@ -654,6 +654,7 @@ export const enum EmailMessage {
ANNOUNCEMENT = "اعلان",
PAYMENT_REMINDER = "یادآوری پرداخت",
PAYMENT_CANCELLATION = "لغو پرداخت",
VERIFY_OTP = "تایید کد یکبار مصرف",
}
export const enum FinancialMessage {
+11
View File
@@ -0,0 +1,11 @@
import { ApiProperty } from "@nestjs/swagger";
import { IsEmail, IsNotEmpty } from "class-validator";
import { AuthMessage } from "../../../common/enums/message.enum";
export class ForgetPasswordDto {
@IsNotEmpty({ message: AuthMessage.EMAIL_NOT_EMPTY })
@IsEmail(undefined, { message: AuthMessage.INVALID_EMAIL_FORMAT })
@ApiProperty({ description: "email of user", default: "admin-dsc@gmail.com" })
email: string;
}
+22
View File
@@ -0,0 +1,22 @@
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
import { IsEmail, IsNotEmpty, IsNumberString, IsString, Length } from "class-validator";
import { AuthMessage } from "../../../common/enums/message.enum";
export class VerifyForgotPasswordOtpDto {
@IsNotEmpty({ message: AuthMessage.EMAIL_NOT_EMPTY })
@ApiProperty()
@IsEmail()
email: string;
@ApiProperty({ description: "OTP code received via SMS", example: "56893" })
@IsNotEmpty({ message: AuthMessage.OTP_NOT_EMPTY })
@IsNumberString(undefined, { message: AuthMessage.OTP_FORMAT_INVALID })
@Length(5, 5, { message: AuthMessage.OTP_FORMAT_INVALID })
code: string;
@IsString()
@IsNotEmpty()
@ApiPropertyOptional({ description: "new password", example: "abc123" })
newPassword: string;
}
+13 -9
View File
@@ -14,6 +14,8 @@ import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
import { RefreshTokenRateLimit, StrictRateLimit } from "../../common/decorators/rate-limit.decorator";
import { UserDec } from "../../common/decorators/user.decorator";
import { User } from "../users/entities/user.entity";
import { ForgetPasswordDto } from "./DTO/forget-password.dto";
import { VerifyForgotPasswordOtpDto } from "./DTO/verify-forgot-otp.dto";
@ApiTags("Auth")
@Controller("auth")
@@ -22,7 +24,7 @@ export class AuthController {
constructor(
private readonly authService: AuthService,
private readonly ssoService: SSOService,
) {}
) { }
@ApiOperation({ summary: "request to login with otp" })
@HttpCode(HttpStatus.OK)
@@ -92,15 +94,17 @@ export class AuthController {
}
//***************************** */
// @Post("forgot-password")
// async forgotPassword(@Body() forgotPasswordDto: ForgotPasswordDto) {
// return this.authService.forgotPassword(forgotPasswordDto);
// }
@ApiOperation({ summary: "Generate otp and send vial email" })
@Post("forgot-password")
async forgotPassword(@Body() dto: ForgetPasswordDto) {
return this.authService.requestForgotPasswordOtp(dto);
}
// @Post("reset-password")
// async resetPassword(@Body() resetPasswordDto: ResetPasswordDto) {
// return this.authService.resetPassword(resetPasswordDto);
// }
@ApiOperation({ summary: "verify otp and update password" })
@Post("reset-password")
async resetPassword(@Body() resetPasswordDto: VerifyForgotPasswordOtpDto) {
return this.authService.verifyForgotPasswordOtp(resetPasswordDto);
}
@ApiOperation({ summary: "Generate SSO token for client application" })
@HttpCode(HttpStatus.OK)
+58 -2
View File
@@ -17,6 +17,10 @@ import { ChangePasswordDto } from "../DTO/change-password.dto";
import { CheckUserExistDto, LoginPasswordDTO } from "../DTO/loginPassword.dto";
import { RequestOtpDto } from "../DTO/request-otp.dto";
import { VerifyOtpDto } from "../DTO/verify-otp.dto";
import { ForgetPasswordDto } from "../DTO/forget-password.dto";
import { VerifyForgotPasswordOtpDto } from "../DTO/verify-forgot-otp.dto";
import { EmailService } from "../../utils/providers/email.service";
@Injectable()
export class AuthService {
private readonly logger = new Logger(AuthService.name);
@@ -29,7 +33,8 @@ export class AuthService {
private readonly dataSource: DataSource,
private readonly notificationQueue: NotificationQueue,
private readonly referralsService: ReferralsService,
) {}
private readonly emailService: EmailService,
) { }
//****************** */
//****************** */
@@ -99,7 +104,7 @@ export class AuthService {
await this.notificationQueue.addLoginOtpNotification({ phone, code: otpCode });
return {
message: AuthMessage.OTP_SENT+otpCode,
message: AuthMessage.OTP_SENT + otpCode,
};
}
@@ -244,7 +249,58 @@ export class AuthService {
return user;
}
//****************** */
//****************** */
async requestForgotPasswordOtp(dto: ForgetPasswordDto) {
const { email } = dto;
const user = await this.usersService.findOneWithEmail(email)
if (!user) {
throw new BadRequestException("کاربری با این ایمیل یافت نشد")
}
const existCode = await this.otpService.checkExistOtp(email, "FORGOT_PASSWORD");
if (existCode) {
return {
message: AuthMessage.OTP_ALREADY_SENT,
ttlSecond: existCode,
};
}
const otpCode = await this.otpService.generateAndSetInCache(email, "FORGOT_PASSWORD");
await this.emailService.sendOtpEmail({ userEmail: email, otp: otpCode });
this.logger.log(`otp code: ${otpCode} for email: ${email}`);
return { message: 'success' }
}
async verifyForgotPasswordOtp(dto: VerifyForgotPasswordOtpDto) {
const { code, email, newPassword } = dto;
const isValid = await this.otpService.verifyOtp(email, code, "FORGOT_PASSWORD");
if (!isValid) throw new BadRequestException(AuthMessage.INVALID_OTP);
let user = await this.usersService.findOneWithEmail(email);
if (!user) {
throw new BadRequestException('کاربر پیدا نشد .')
}
const hashedPassword = await this.passwordService.hashPassword(newPassword);
await this.otpService.delOtpFormCache(email, "FORGOT_PASSWORD");
await this.usersService.updateUserPassword(user.id, hashedPassword);
return {
message: AuthMessage.PASSWORD_CHANGED_SUCCESSFULLY
};
}
//****************** */
//****************** */
private checkUserIsAdmin(roles: Role[]) {
+23 -21
View File
@@ -1,23 +1,25 @@
<html dir="rtl" lang="fa">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ورود به سیستم</title>
</head>
<body
style="font-family: 'IRANSans', Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px; text-align: center;"
>
<div style="text-align: center; margin-bottom: 30px;">
<h1>ورود به سیستم</h1>
</div>
<div style="background-color: #f9f9f9; padding: 20px; border-radius: 5px;">
<p>سلام،</p>
<p>ورود به سیستم در تاریخ و ساعت زیر انجام شد:</p>
<div style="font-size: 20px; color: #2c3e50; text-align: center; margin: 20px 0;">{{date}}</div>
<p>با تشکر</p>
</div>
<div style="text-align: center; margin-top: 30px; font-size: 12px; color: #666;">
<p>این ایمیل به صورت خودکار ارسال شده است. لطفاً به آن پاسخ ندهید.</p>
</div>
</body>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ورود به سیستم</title>
</head>
<body
style="font-family: 'IRANSans', Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px; text-align: center;">
<div style="text-align: center; margin-bottom: 30px;">
<h1>ورود به سیستم</h1>
</div>
<div style="background-color: #f9f9f9; padding: 20px; border-radius: 5px;">
<p>سلام،</p>
<p>کد تایید :</p>
<div style="font-size: 20px; color: #2c3e50; text-align: center; margin: 20px 0;">{{otp}}</div>
</div>
<div style="text-align: center; margin-top: 30px; font-size: 12px; color: #666;">
<p>این ایمیل به صورت خودکار ارسال شده است. لطفاً به آن پاسخ ندهید.</p>
</div>
</body>
</html>
+23
View File
@@ -0,0 +1,23 @@
<html dir="rtl" lang="fa">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>کد تایید تغییر رمز عبور</title>
</head>
<body
style="font-family: 'IRANSans', Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px; text-align: center; dir:rtl;"
>
<div style="text-align: center; margin-bottom: 30px;">
<h1>ورود به سیستم</h1>
</div>
<div style="background-color: #f9f9f9; padding: 20px; border-radius: 5px;">
<p>سلام،</p>
<p>ورود به سیستم در تاریخ و ساعت زیر انجام شد:</p>
<div style="font-size: 20px; color: #2c3e50; text-align: center; margin: 20px 0;">{{date}}</div>
<p>با تشکر</p>
</div>
<div style="text-align: center; margin-top: 30px; font-size: 12px; color: #666;">
<p>این ایمیل به صورت خودکار ارسال شده است. لطفاً به آن پاسخ ندهید.</p>
</div>
</body>
</html>
+16 -1
View File
@@ -16,7 +16,7 @@ import { IBaseNotificationData } from "../../notifications/interfaces/ISendNotif
@Injectable()
export class EmailService {
private readonly logger = new Logger(EmailService.name);
constructor(private readonly mailerService: MailerService) {}
constructor(private readonly mailerService: MailerService) { }
async sendVerificationEmail(to: string, link: string, fullName: string): Promise<SentMessageInfo> {
try {
@@ -174,4 +174,19 @@ export class EmailService {
// throw new InternalServerErrorException("error in sending admin notification email");
}
}
async sendOtpEmail({ userEmail, otp }: { userEmail: string, otp: string }) {
try {
await this.mailerService.sendMail({
to: userEmail,
subject: EmailMessage.VERIFY_OTP,
template: "otp",
context: {
otp,
},
});
} catch (error) {
this.logger.error("error in sending otp email", error);
}
}
}