chore: add verfication email for user profile
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
/************************************************************ */
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user