chore: add verfication email for user profile

This commit is contained in:
mahyargdz
2025-02-18 10:16:05 +03:30
parent ff96fd6c32
commit cad8e15941
15 changed files with 173 additions and 31 deletions
+76 -3
View File
@@ -1,4 +1,8 @@
import { createHmac } from "node:crypto";
import { BadRequestException, Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { FastifyReply } from "fastify";
import slugify from "slugify";
import { In, Not, QueryRunner } from "typeorm";
@@ -13,6 +17,7 @@ import {
import { AddressService } from "../../address/providers/address.service";
import { CompleteRegistrationDto } from "../../auth/DTO/complete-register.dto";
import { UserSettingsService } from "../../settings/providers/user-settings.service";
import { EmailService } from "../../utils/providers/email.service";
import { PaginationUtils } from "../../utils/providers/pagination.utils";
import { PasswordService } from "../../utils/providers/password.service";
import { WalletsService } from "../../wallets/providers/wallets.service";
@@ -24,8 +29,10 @@ import { CreateRoleDto } from "../DTO/create-role.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";
import { Role } from "../entities/role.entity";
import { User } from "../entities/user.entity";
import { RoleEnum } from "../enums/role.enum";
@@ -50,6 +57,8 @@ export class UsersService {
private readonly realUserRepository: RealUserRepository,
private readonly legalUserRepository: LegalUserRepository,
private readonly addressService: AddressService,
private readonly emailService: EmailService,
private readonly configService: ConfigService,
) {}
/************************************************************ */
@@ -94,14 +103,15 @@ export class UsersService {
/************************************************************ */
async updateProfile(userId: string, updateProfileDto: UpdateProfileDto) {
const user = await this.userRepository.findOneBy({ id: userId });
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
//
if (updateProfileDto.userName) {
//
updateProfileDto.userName = slugify(updateProfileDto.userName, { lower: true, trim: true });
const existUserName = await this.userRepository.findOneBy({ userName: updateProfileDto.userName, id: Not(userId) });
if (existUserName) throw new BadRequestException(UserMessage.USERNAME_EXIST);
}
const user = await this.userRepository.findOneBy({ id: userId });
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
await this.userRepository.save({ ...user, ...updateProfileDto });
//
@@ -111,6 +121,54 @@ export class UsersService {
}
/************************************************************ */
async updateEmail(userId: string, updateDto: UpdateEmailDto) {
const { email } = updateDto;
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);
await this.emailService.sendVerificationEmail(email, emailLink, `${user.firstName} ${user.lastName}`);
await this.userRepository.save({ ...user, email });
return {
message: UserMessage.VERIFY_EMAIL_LINK_SENT,
email,
};
}
/************************************************************ */
//TODO:fix this later
async verifyEmail(queryDto: EmailVerifyQueryDto, rep: FastifyReply) {
const { token, userId, ts } = queryDto;
const user = await this.userRepository.findOneBy({ id: userId });
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
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);
}
const payload = `${user.id}:${timestamp}`;
const expectedHash = createHmac("sha256", secret).update(payload).digest("hex");
if (expectedHash !== token) throw new BadRequestException(UserMessage.INVALID_EMAIL_TOKEN);
user.emailVerified = true;
await this.userRepository.save(user);
// return { message: UserMessage.EMAIL_VERIFIED };
return rep.redirect("https://google.com");
}
/************************************************************ */
async findOneWithEmail(email: string): Promise<User | null> {
const user = await this.userRepository.findOneWithEmail(email);
return user;
@@ -446,5 +504,20 @@ export class UsersService {
};
}
private async createEmailHashLink(userId: string) {
const secret = this.configService.getOrThrow<string>("EMAIL_SECRET");
const timestamp = Date.now();
const payload = `${userId}:${timestamp}`;
const hash = createHmac("sha256", secret).update(payload).digest("hex");
const url = new URL(`${this.configService.getOrThrow<string>("SITE_URL")}/users/verify-email`);
url.searchParams.append("token", hash);
url.searchParams.append("userId", userId);
url.searchParams.append("ts", timestamp.toString());
return url.toString();
}
/************************************************************ */
}