fix: the email vaerification logic error
This commit is contained in:
Regular → Executable
+46
-36
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user