fix: the email vaerification logic error

This commit is contained in:
mahyargdz
2025-02-26 15:25:18 +03:30
parent f8a1a7b784
commit 93c5ad49d3
320 changed files with 215 additions and 92 deletions
+46 -36
View File
@@ -1,4 +1,4 @@
import { createHmac } from "node:crypto";
import { createHmac, randomBytes } from "node:crypto";
import { BadRequestException, HttpStatus, Injectable, Logger } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
@@ -20,12 +20,14 @@ import { CompleteRegistrationDto } from "../../auth/DTO/complete-register.dto";
import { RequestOtpDto } from "../../auth/DTO/request-otp.dto";
import { VerifyOtpDto } from "../../auth/DTO/verify-otp.dto";
import { UserSettingsService } from "../../settings/providers/user-settings.service";
import { CacheService } from "../../utils/providers/cache.service";
import { EmailService } from "../../utils/providers/email.service";
import { OTPService } from "../../utils/providers/otp.service";
import { PaginationUtils } from "../../utils/providers/pagination.utils";
import { PasswordService } from "../../utils/providers/password.service";
import { SmsService } from "../../utils/providers/sms.service";
import { WalletsService } from "../../wallets/providers/wallets.service";
import { ChangeEmailDto } from "../DTO/change-email.dto";
import { CheckValidityDTO } from "../DTO/check-validity.dto";
import { CreateAdminDto } from "../DTO/create-admin.dto";
import { CreateLegalUserDto } from "../DTO/create-legal-user.dto";
@@ -35,7 +37,6 @@ import { CreateCustomerDto } from "../DTO/create-user.dto";
import { SearchAdminQueryDto } from "../DTO/search-admins-query.dto";
import { SearchCustomersDto } from "../DTO/search-customers.dto";
import { SearchRolesQueryDto } from "../DTO/search-roles.dto";
import { UpdateEmailDto } from "../DTO/update-email.dto";
import { UpdateProfileDto } from "../DTO/update-profile.dto";
import { CreateUserGroupDto } from "../DTO/user-group.dto";
import { EmailVerifyQueryDto } from "../DTO/verify-email-query.dto";
@@ -68,6 +69,7 @@ export class UsersService {
private readonly configService: ConfigService,
private readonly otpService: OTPService,
private readonly smsService: SmsService,
private readonly cacheService: CacheService,
) {}
/************************************************************ */
@@ -138,18 +140,23 @@ export class UsersService {
}
/************************************************************ */
async updateEmail(userId: string, updateDto: UpdateEmailDto) {
const { email } = updateDto;
async changeEmail(userId: string, changeDto: ChangeEmailDto) {
const { email } = changeDto;
const user = await this.userRepository.findOneBy({ id: userId });
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
//
const existEmail = await this.userRepository.findOneBy({ email, id: Not(userId) });
if (existEmail) throw new BadRequestException(UserMessage.EMAIL_EXIST);
//
const emailLink = await this.createEmailHashLink(user.id);
const emailLink = await this.createEmailHashLink(email);
await this.emailService.sendVerificationEmail(email, emailLink, `${user.firstName} ${user.lastName}`);
await this.userRepository.save({ ...user, email });
if (user.email !== email) {
user.email = email;
user.emailVerified = false;
}
await this.userRepository.save(user);
return {
message: UserMessage.VERIFY_EMAIL_LINK_SENT,
@@ -157,37 +164,40 @@ export class UsersService {
};
}
/************************************************************ */
//TODO:fix this later
async verifyEmail(queryDto: EmailVerifyQueryDto, rep: FastifyReply) {
const frontUrl = this.configService.getOrThrow<string>("FRONT_URL");
const { token, userId, ts } = queryDto;
const user = await this.userRepository.findOneBy({ id: userId });
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
async verifyEmail(queryDto: EmailVerifyQueryDto, rep: FastifyReply) {
const frontUrl = this.configService.getOrThrow<string>("SITE_URL");
const { token, email } = queryDto;
const user = await this.userRepository.findOneBy({ email, emailVerified: true });
if (user) throw new BadRequestException(UserMessage.EMAIL_EXIST);
const cachedData = await this.cacheService.get(`EMAIL_VERIFY:${email}`);
if (!cachedData) throw new BadRequestException(UserMessage.INVALID_EMAIL_TOKEN);
const { hash, nonce } = JSON.parse(cachedData as string);
const secret = this.configService.getOrThrow<string>("EMAIL_SECRET");
// Check if timestamp is within allowed time window (e.g., 24 hours)
const timestamp = parseInt(ts, 10);
if (Date.now() - timestamp > 24 * 60 * 60 * 1000) {
throw new BadRequestException(UserMessage.EXPIRED_EMAIL_TOKEN);
}
if (hash !== token) throw new BadRequestException(UserMessage.INVALID_EMAIL_TOKEN);
const payload = `${user.id}:${timestamp}`;
const expectedHash = createHmac("sha256", secret).update(payload).digest("hex");
const expectedHash = createHmac("sha256", secret).update(`${email}:${nonce}`).digest("hex");
if (expectedHash !== hash) throw new BadRequestException(UserMessage.INVALID_EMAIL_TOKEN);
if (expectedHash !== token) throw new BadRequestException(UserMessage.INVALID_EMAIL_TOKEN);
const userToUpdate = await this.userRepository.findOneBy({ email });
if (!userToUpdate) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
user.emailVerified = true;
await this.userRepository.save(user);
userToUpdate.emailVerified = true;
await this.userRepository.save(userToUpdate);
// return { message: UserMessage.EMAIL_VERIFIED };
return rep.status(HttpStatus.FOUND).redirect(frontUrl);
await this.cacheService.del(`EMAIL_VERIFY:${email}`);
return rep.status(HttpStatus.FOUND).redirect(`${frontUrl}/profile`);
}
/************************************************************ */
async updatePhone(userId: string, requestOtpDto: RequestOtpDto) {
async changePhone(userId: string, requestOtpDto: RequestOtpDto) {
const { phone } = requestOtpDto;
const user = await this.userRepository.findOneBy({ id: userId });
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
@@ -570,6 +580,7 @@ export class UsersService {
paginate: true,
};
}
/************************************************************ */
async createRealUserData(createDto: CreateRealUserDto, userId: string) {
const userExist = await this.userRepository.findOneBy({
@@ -655,17 +666,16 @@ export class UsersService {
/************************************************************ */
private async createEmailHashLink(userId: string) {
private async createEmailHashLink(email: string) {
const secret = this.configService.getOrThrow<string>("EMAIL_SECRET");
const timestamp = Date.now();
const payload = `${userId}:${timestamp}`;
const nonce = randomBytes(16).toString("hex");
const hash = createHmac("sha256", secret).update(`${email}:${nonce}`).digest("hex");
const hash = createHmac("sha256", secret).update(payload).digest("hex");
const url = new URL(`${this.configService.getOrThrow<string>("SITE_URL")}/users/verify-email`);
const url = new URL(`${this.configService.getOrThrow<string>("API_URL")}/users/verify-email`);
url.searchParams.append("token", hash);
url.searchParams.append("userId", userId);
url.searchParams.append("ts", timestamp.toString());
url.searchParams.append("email", email);
await this.cacheService.set(`EMAIL_VERIFY:${email}`, JSON.stringify({ hash, email, nonce }), 5 * 60 * 1000);
return url.toString();
}