From 1ef3529c449dd7b8bbe3e2c8e4a97bf5a14ac96a Mon Sep 17 00:00:00 2001 From: Bertin M Date: Mon, 20 May 2024 20:07:44 +0200 Subject: [PATCH] Ft: auth (new user needs to verify one's account before login --- __test__/cart.test.ts | 3 + __test__/payment.test.ts | 3 + __test__/product.test.ts | 29 +++++- __test__/user.test.ts | 44 ++++++++- src/controllers/userControllers.ts | 30 +++++- src/docs/swagger.ts | 5 +- src/docs/users.ts | 36 +++++++ src/email-templates/verifyUser.ts | 95 +++++++++++++++++++ src/middlewares/isVerified.ts | 16 ++++ src/routes/userRoutes.ts | 10 +- src/schemas/signUpSchema.ts | 1 + .../h20240519174339-add-is-verified.js | 23 +++++ src/sequelize/models/users.ts | 7 ++ .../seeders/b20240412144111-demo-user.js | 4 + src/services/user.service.ts | 53 ++++++++++- src/utils/generateResetToken.ts | 10 ++ 16 files changed, 354 insertions(+), 15 deletions(-) create mode 100644 src/email-templates/verifyUser.ts create mode 100644 src/middlewares/isVerified.ts create mode 100644 src/sequelize/migrations/h20240519174339-add-is-verified.js diff --git a/__test__/cart.test.ts b/__test__/cart.test.ts index 418f085..95547bf 100644 --- a/__test__/cart.test.ts +++ b/__test__/cart.test.ts @@ -26,6 +26,7 @@ describe("testing cart", () => { name: "admin123", username: "admin123", email: "admin1@example.com", + isVerified:true, password: await bcrypt.hash("password", 10), roleId: 3, }; @@ -33,12 +34,14 @@ describe("testing cart", () => { const testBuyer = { name: "buyer123", username: "buyer123", + isVerified:true, email: "buyer1@example.com", password: await bcrypt.hash("password", 10), }; const testSeller = { name: "seller123", username: "seller123", + isVerified:true, email: "seller123@example.com", password: await bcrypt.hash("password", 10), }; diff --git a/__test__/payment.test.ts b/__test__/payment.test.ts index 4dc8e40..c6ef99e 100644 --- a/__test__/payment.test.ts +++ b/__test__/payment.test.ts @@ -22,6 +22,7 @@ describe("test stripe api payment", () => { const testAdmin = { name: "admin123", username: "admin123", + isVerified:true, email: "admin1@example.com", password: await bcrypt.hash("password", 10), roleId: 3, @@ -30,6 +31,7 @@ describe("test stripe api payment", () => { const testBuyer = { name: "buyer123", username: "buyer123", + isVerified:true, email: "buyer1@example.com", password: await bcrypt.hash("password", 10), }; @@ -37,6 +39,7 @@ describe("test stripe api payment", () => { const testSeller = { name: "seller123", username: "seller123", + isVerified:true, email: "seller123@example.com", password: await bcrypt.hash("password", 10), }; diff --git a/__test__/product.test.ts b/__test__/product.test.ts index d4045a9..113a8a7 100644 --- a/__test__/product.test.ts +++ b/__test__/product.test.ts @@ -15,10 +15,12 @@ import { placeOrder } from "../src/services/payment.service"; import Cart from "../src/sequelize/models/Cart"; import CartItem from "../src/sequelize/models/CartItem"; import OrderItem from "../src/sequelize/models/orderItems"; +import { generateVerificationToken } from "../src/utils/generateResetToken"; const userData: any = { name: "yvanna", username: "testuser", + isVerified:true, email: "test1@gmail.com", role:"seller", password: "test1234", @@ -27,6 +29,7 @@ const userData: any = { const dummySeller = { name: "dummy1234", username: "username1234", + isVerified:true, email: "soleilcyber00@gmail.com", password: "1234567890", }; @@ -35,6 +38,7 @@ const product:any = { name: "pens", images: ["image1.jpg", "image2.jpg", "image3.jpg", "image4.jpg"], stockQuantity: 8, + price: 5000, discount: 3.5, categoryID: 1, @@ -43,6 +47,7 @@ const product:any = { const dummyBuyer = { name: "test user", username: "testUser", + isVerified:true, email: "soleil@soleil0w.com", password: "soleil00", } @@ -60,6 +65,7 @@ describe("Testing product Routes", () => { await connect(); const testAdmin = { name: "admin123", + isVerified:true, username: "admin123", email: "admin1@example.com", password: await bcrypt.hash("password", 10), @@ -76,8 +82,8 @@ describe("Testing product Routes", () => { ]) await User.create(testAdmin); - - const dummy = await request(app).post("/api/v1/users/register").send(dummySeller); + await User.create(dummySeller); + // await request(app).post("/api/v1/users/register").send(dummySeller); await Product.destroy({}); await Category.destroy({truncate:true}); } catch (error) { @@ -98,12 +104,27 @@ describe("Testing product Routes", () => { expect(response.status).toBe(201); }, 20000); + it("It should verify user account.",async()=>{ + const token = generateVerificationToken(userData.email, 60); + const response = await request(app) + .get(`/api/v1/users/verify-user?token=${token}`) + expect(response.status).toBe(200) + expect(response.body.message).toBe('User verified successfully.') + },60000) + test('should return 201 and register a dummy buyer user', async () => { const response = await request(app) .post("/api/v1/users/register") .send(dummyBuyer); expect(response.status).toBe(201); }) + it("It should verify user account.",async()=>{ + const token = generateVerificationToken(dummyBuyer.email, 60); + const response = await request(app) + .get(`/api/v1/users/verify-user?token=${token}`) + expect(response.status).toBe(200) + expect(response.body.message).toBe('User verified successfully.') + },60000) let buyerToken: any; test("should login an buyer", async () =>{ @@ -128,6 +149,7 @@ describe("Testing product Routes", () => { password: "password" }) adminToken = response.body.token; + expect(response.status).toBe(200) }); test("should update dummyseller's role to seller", async () => { @@ -146,6 +168,7 @@ describe("Testing product Routes", () => { }) .set("Authorization", "Bearer " + adminToken); expect(response.status).toBe(200); + expect(response.body.message).toBe('User role updated successfully'); }); @@ -472,7 +495,7 @@ test('It should return status 200 for removed category',async() =>{ }) it("changing product availability of product which does not exist", async ()=>{ const response = await request(app) - .patch(`/api/v1/products/${91}/status`) + .patch(`/api/v1/products/${4444444}/status`) .set("Authorization", "Bearer " + token); expect(response.body.message).toBe('Product not found') }) diff --git a/__test__/user.test.ts b/__test__/user.test.ts index 0019e84..b0ba0d2 100644 --- a/__test__/user.test.ts +++ b/__test__/user.test.ts @@ -15,7 +15,7 @@ import { QueryTypes } from "sequelize"; // import redisClient from "../src/config/redis"; import Redis from "ioredis"; import { env } from "../src/utils/env"; -import { generateResetToken } from "../src/utils/generateResetToken"; +import { generateResetToken, generateVerificationToken } from "../src/utils/generateResetToken"; let redisClient:any; @@ -23,6 +23,7 @@ let redisClient:any; const userData: any = { name: "yvanna5", username: "testuser5", + isVerified:true, email: "test15@gmail.com", password: "test12345", }; @@ -31,11 +32,13 @@ const userData: any = { const dummySeller = { name: "dummy1234", username: "username1234", + isVerified:true, email: "soleilcyber00@gmail.com", password: "1234567890", }; const userTestData = { newPassword: "Test@123", + isVerified:true, confirmPassword: "Test@123", wrongPassword: "Test456", }; @@ -107,6 +110,14 @@ describe("Testing user Routes", () => { expect(response.status).toBe(201); }, 20000); + it("It should verify user account.",async()=>{ + const token = generateVerificationToken(userData.email, 60); + const response = await request(app) + .get(`/api/v1/users/verify-user?token=${token}`) + expect(response.status).toBe(200) + expect(response.body.message).toBe('User verified successfully.') + },60000) + test("should return 409 when registering with an existing email", async () => { User.create(userData); @@ -135,7 +146,8 @@ describe("Testing user Routes", () => { email: userData.email, password: userData.password, }); - expect(response.status).toBe(200); + // expect(response.status).toBe(200); + expect(response.body.message).toBe("Logged in"); token = response.body.token; }); @@ -206,7 +218,7 @@ describe("Testing user Routes", () => { test("should login an Admin", async () =>{ const response = await request(app).post("/api/v1/users/login").send({ email: "admin1@example.com", - password: "password" + password: "password" }) adminToken = response.body.token; }); @@ -216,7 +228,7 @@ describe("Testing user Routes", () => { email: dummySeller.email, password: dummySeller.password, }); - expect(logDummySeller.status).toBe(200); + expect(logDummySeller.body.message).toBe("Logged in"); const dummySellerId = logDummySeller.body.userInfo.id; const response = await request(app) @@ -225,7 +237,7 @@ describe("Testing user Routes", () => { roleId: 2, }) .set("Authorization", "Bearer " + adminToken); - expect(response.status).toBe(200); + expect(response.body.message).toBe('User role updated successfully'); }); @@ -548,6 +560,28 @@ describe('Patch /api/v1/users/reset-password', () => { },60000); }); +describe("Verifying user account",()=>{ + it("It should verify user account.",async()=>{ + await User.create(userData) + const token = generateVerificationToken(userData.email, 60); + const response = await request(app) + .get(`/api/v1/users/verify-user?token=${token}`) + expect(response.status).toBe(200) + expect(response.body.message).toBe('User verified successfully.') + },60000) + + it("It should send a verification link.",async()=>{ + const response = await request(app) + .post('/api/v1/users/verify-user-email') + .send({ + email:userData.email + }) + expect(response.status).toBe(201) + expect(response.body.message).toBe("Verification email sent successfully.") + },60000) + +}) + afterAll(async () => { try { await sequelize.query('TRUNCATE TABLE profiles, users CASCADE'); diff --git a/src/controllers/userControllers.ts b/src/controllers/userControllers.ts index 26219d5..6def064 100644 --- a/src/controllers/userControllers.ts +++ b/src/controllers/userControllers.ts @@ -105,7 +105,7 @@ export const createUserController = async (req: Request, res: Response) => { } return res.status(201).json({ status: 201, - message: "User successfully created." + message: "User successfully created. check your inbox to verify your email" }); } catch (err: any) { if (err.name === "UnauthorizedError" && err.message === "User already exists") { @@ -190,6 +190,7 @@ export const handleSuccess = async (req: Request, res: Response) => { name: user.displayName, email: user.emails[0].value, username: user.name.familyName, + isVerified:true, //@ts-ignore password: null }); @@ -401,3 +402,30 @@ export const resetPasswordController = async (req: Request, res: Response): Prom return res.status(500).json({ message: 'Internal server error.' }); } }; + +export const verifyUserEmailController = async (req: Request, res: Response) => { + try { + // Extract email from the request body + const {email} = req.body; + + const { error } = Emailschema.validate(req.body); + if (error) { + const cleanErrorMessage = error.details.map(detail => detail.message.replace(/['"]/g, '').trim()).join(', ') + return res.status(400).json({ message: cleanErrorMessage }); + } + const result = await userService.verifyUserEmail(email); + res.status(result.status).json({ message: result.message }); + } catch (error) { + res.status(500).json({ error: 'Failed to send verification email.' }); + } +}; +export const verifyUserController = async (req: Request, res: Response): Promise => { + try { + // Extract token from the request query + const token = req.query.token as string; + const result = await userService.verifyNewUser(token); + res.status(result.status).json({ message: result.message }); + } catch (error) { + res.status(500).json({ error: 'Failed to verify new user.' }); + } +}; \ No newline at end of file diff --git a/src/docs/swagger.ts b/src/docs/swagger.ts index b4ba080..81d734b 100644 --- a/src/docs/swagger.ts +++ b/src/docs/swagger.ts @@ -19,6 +19,7 @@ import { sendResetLink, updateForgotPassword, verifyUserAccessToken, + verifyUserEmail, } from "./users"; import { getProducts, @@ -39,7 +40,6 @@ import { getCategories, addCategories, getSingleCategory, updateCategories, dele deleteRole } from "./roledoc"; import { AddToWishes, deleteWish, getWishes, getWishesByProduct, wishSchema } from "./wishes"; -import { joinChats } from "./chats"; import { addItemToCartDoc, clearAllProductFromCartDoc, removeProductFromCartDoc, updateProductQuantityDoc, viewCartDoc } from "./cart"; import { getAllNotifications, readNotification } from "./notifications"; import { homepage } from "./home"; @@ -115,6 +115,9 @@ const options = { "/api/v1/users/reset-password": { patch: updateForgotPassword, }, + "/api/v1/users/verify-user-email": { + post: verifyUserEmail, + }, "/api/v1/users/me": { post: verifyUserAccessToken, diff --git a/src/docs/users.ts b/src/docs/users.ts index 199fc96..6db6fdf 100644 --- a/src/docs/users.ts +++ b/src/docs/users.ts @@ -481,3 +481,39 @@ export const updateForgotPassword = { } } }; +export const verifyUserEmail = { + tags: ["Users"], + summary: "Verify user email", + description: "Verify user email using the provided email address", + requestBody: { + required: true, + content: { + "application/json": { + schema: { + type: "object", + properties: { + email: { + type: "string", + description: "The user's email address" + } + }, + required: ["email"] + } + } + } + }, + responses: { + 200: { + description: "Verification email sent successfully" + }, + 400: { + description: "Invalid email address" + }, + 404: { + description: "User not found" + }, + 500: { + description: "Internal server error" + } + } +}; \ No newline at end of file diff --git a/src/email-templates/verifyUser.ts b/src/email-templates/verifyUser.ts new file mode 100644 index 0000000..40cc678 --- /dev/null +++ b/src/email-templates/verifyUser.ts @@ -0,0 +1,95 @@ +// emailTemplates/verifyUser.ts +const verifyUserEmailTemplate = (username:string,verificationLink:string) => { + return ` + + + + + + Verify Your Email + + + +
+
+ Company Logo +
+
+

Welcome, ${username}!

+

Thank you for registering with us. Please verify your email address to complete your registration.

+

Click the button below to verify your email address:

+ Verify Email +

If the button above does not work, copy and paste the following link into your browser:

+

${verificationLink}

+
+ +
+ + + `; + + +}; + +export default verifyUserEmailTemplate; + + +export const generateEmailVerificationEmail = (Username:string)=> { + return ` +Dear ${Username}, +
+ +Thank you for verifying your email address. You are now able to start using our system. +
+ +If you have any questions, feel free to reach out to our support team. +
+ +Best regards, +
+ATLP-eccommerce + `; +} \ No newline at end of file diff --git a/src/middlewares/isVerified.ts b/src/middlewares/isVerified.ts new file mode 100644 index 0000000..cab5ce5 --- /dev/null +++ b/src/middlewares/isVerified.ts @@ -0,0 +1,16 @@ +import { Request, Response, NextFunction } from 'express'; +import User from '../sequelize/models/users'; + +export const isVerified = async (req: Request, res: Response, next: NextFunction) => { + // @ts-ignore + const { email} = req.body; + const user = await User.findOne({ + where:{ + email:email + } + }); + if (user?.isVerified === false) { + return res.status(403).json({ message: 'Account is not verified' }); + } + next(); + }; \ No newline at end of file diff --git a/src/routes/userRoutes.ts b/src/routes/userRoutes.ts index d41bdca..19c9724 100644 --- a/src/routes/userRoutes.ts +++ b/src/routes/userRoutes.ts @@ -1,5 +1,5 @@ import { Router } from "express"; -import { fetchAllUsers, createUserController, userLogin, updatePassword, tokenVerification, handleSuccess, handleFailure,updateProfileController, getProfileController, otpVerification, changeUserAccountStatus, logout, sendResetLinkEmail, resetPasswordController } from "../controllers/userControllers"; +import { fetchAllUsers, createUserController, userLogin, updatePassword, tokenVerification, handleSuccess, handleFailure,updateProfileController, getProfileController, otpVerification,updateUserRole, changeUserAccountStatus, logout, sendResetLinkEmail, resetPasswordController, verifyUserEmailController, verifyUserController } from "../controllers/userControllers"; import { emailValidation, validateSchema } from "../middlewares/validator"; import { isLoggedIn } from "../middlewares/isLoggedIn"; import { passwordUpdateSchema } from "../schemas/passwordUpdate"; @@ -9,14 +9,13 @@ import logInSchema from "../schemas/loginSchema"; import { profileSchemas, signUpSchema } from "../schemas/signUpSchema"; import upload from "../middlewares/multer"; import isUploadedFileImage from "../middlewares/isImage"; -import bodyParser from "body-parser"; import { isAdmin } from "../middlewares/isAdmin"; import { roleUpdateSchema } from "../schemas/userRoleUpdateSchema"; -import { updateUserRole } from "../controllers/userControllers"; import { roleExist } from "../middlewares/roleExist"; import { userExist } from "../middlewares/userExist"; import { isDisabled } from "../middlewares/isDisabled"; import { verifyToken } from "../middlewares/verifyToken"; +import { isVerified } from "../middlewares/isVerified"; @@ -24,7 +23,7 @@ const userRoutes = Router(); userRoutes.get("/", fetchAllUsers); userRoutes.put("/passwordupdate", isLoggedIn, validateSchema(passwordUpdateSchema), updatePassword) -userRoutes.post("/login", emailValidation,validateSchema(logInSchema),isDisabled,userLogin); +userRoutes.post("/login", emailValidation,validateSchema(logInSchema),isDisabled,isVerified,userLogin); userRoutes.post("/register", emailValidation, validateSchema(signUpSchema), createUserController); userRoutes.put("/passwordupdate", isLoggedIn, validateSchema(passwordUpdateSchema), updatePassword); userRoutes.get("/2fa-verify/:token",tokenVerification); @@ -51,6 +50,9 @@ userRoutes.get("/auth/google/success", handleSuccess); userRoutes.get("/auth/google/failure", handleFailure); userRoutes.post('/password-reset-link', sendResetLinkEmail); userRoutes.patch('/reset-password', resetPasswordController); +userRoutes.post('/verify-user-email', verifyUserEmailController); +userRoutes.get('/verify-user', verifyUserController); + userRoutes.get("/me", verifyToken); diff --git a/src/schemas/signUpSchema.ts b/src/schemas/signUpSchema.ts index 9d1c448..b2901eb 100644 --- a/src/schemas/signUpSchema.ts +++ b/src/schemas/signUpSchema.ts @@ -5,6 +5,7 @@ export const signUpSchema = Joi.object({ username: Joi.string().min(4).required(), email: Joi.string().min(6).required().email(), password: Joi.string().min(6).max(20).required(), + isVerified:Joi.boolean().optional(), role: Joi.string().optional(), }).options({ allowUnknown: false }); diff --git a/src/sequelize/migrations/h20240519174339-add-is-verified.js b/src/sequelize/migrations/h20240519174339-add-is-verified.js new file mode 100644 index 0000000..34f3b32 --- /dev/null +++ b/src/sequelize/migrations/h20240519174339-add-is-verified.js @@ -0,0 +1,23 @@ +'use strict'; + +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up(queryInterface, Sequelize) { + const isVerifiedColumnExists = await queryInterface.describeTable("users").then((columns) => { + return "isAvailable" in columns; + }); + + if (!isVerifiedColumnExists) { + await queryInterface.addColumn("users", "isVerified", { + type: Sequelize.BOOLEAN, + allowNull: false, + defaultValue: false, + }); + } + }, + + async down(queryInterface, Sequelize) { + await queryInterface.removeColumn("users", "isVerified"); + }, +}; + diff --git a/src/sequelize/models/users.ts b/src/sequelize/models/users.ts index b8c864b..765a65b 100644 --- a/src/sequelize/models/users.ts +++ b/src/sequelize/models/users.ts @@ -14,6 +14,7 @@ export interface UserAttributes { password: string | undefined; roleId: number | undefined; isActive?:boolean; + isVerified?:boolean; createdAt?: Date; updatedAt?: Date; } @@ -25,6 +26,7 @@ class User extends Model implements UserAttributes { email!: string; password!: string; isActive: boolean | undefined; + isVerified?: boolean | undefined; roleId!: number | undefined; createdAt!: Date | undefined; updatedAt!: Date | undefined; @@ -68,6 +70,11 @@ User.init( allowNull:false, defaultValue:true }, + isVerified:{ + type: DataTypes.BOOLEAN, + allowNull:false, + defaultValue:false + }, createdAt: { allowNull: false, type: DataTypes.DATE, diff --git a/src/sequelize/seeders/b20240412144111-demo-user.js b/src/sequelize/seeders/b20240412144111-demo-user.js index e07fc49..593da67 100644 --- a/src/sequelize/seeders/b20240412144111-demo-user.js +++ b/src/sequelize/seeders/b20240412144111-demo-user.js @@ -30,6 +30,7 @@ module.exports = { email: "soleil@soleil00.com", password: await bcrypt.hash("soleil00", 10), roleId: 3, + isVerified:true, createdAt: new Date(), updatedAt: new Date(), }, @@ -39,6 +40,7 @@ module.exports = { email: "soleil@soleil0w.com", password: await bcrypt.hash("soleil00", 10), roleId: 1, + isVerified:true, createdAt: new Date(), updatedAt: new Date(), }, @@ -48,6 +50,7 @@ module.exports = { email: "mugabo.kefa00@gmail.com", password: await bcrypt.hash("Test@123", 10), roleId: 2, + isVerified:true, createdAt: new Date(), updatedAt: new Date(), }, @@ -57,6 +60,7 @@ module.exports = { email: "jaboinnovates@gmail.com", password: await bcrypt.hash("Test@123", 10), roleId: 2, + isVerified:true, createdAt: new Date(), updatedAt: new Date(), }, diff --git a/src/services/user.service.ts b/src/services/user.service.ts index a30d7c5..843c9ca 100644 --- a/src/services/user.service.ts +++ b/src/services/user.service.ts @@ -10,10 +10,13 @@ import userRoutes from "../routes/userRoutes"; import sequelize from "../config/dbConnection"; import { activationTemplate } from "../email-templates/activation"; import redisClient from "../config/redis"; -import { generateResetToken, verifyResetToken } from "../utils/generateResetToken"; +import { generateResetToken, generateVerificationToken, verifyResetToken } from "../utils/generateResetToken"; import { env } from "../utils/env"; import { generatePasswordResetEmail } from "../email-templates/generatePasswordResetEmail"; import { generatePasswordUpdateEmailContent } from "../email-templates/generatePasswordUpdateEmailContent"; +import verifyUserEmailTemplate, { generateEmailVerificationEmail } from "../email-templates/verifyUser"; +import { generateToken } from "../utils/jsonwebtoken"; +import { IUser } from "../types"; @@ -85,6 +88,13 @@ export const createUserService = async (name: string, email: string, username: s postalCode:"", country: "", }); + + const subject = 'Please verify your email address'; + const token = generateVerificationToken(user.email, 60); + const verificationLink = `${process.env.REMOTE_URL || `${process.env.LOCAL_URL}:${process.env.PORT}`}/api/v1/users/verify-user?token=${token}`; + + await sendEmailService(user,subject,verifyUserEmailTemplate(user.username,verificationLink)) + return user; } @@ -257,3 +267,44 @@ export const resetPassword = async (token: string, newPassword: string): Promise } }; +export const verifyNewUser = async (token: string) => { + + try { + const decodedToken = verifyResetToken(token); + const isBlacklisted = await isTokenBlacklisted(token); + if (!decodedToken || isBlacklisted) { + return { status: 400, message: 'Invalid token.' }; + } + const user:any = { + email:decodedToken.email + } + const subject = `Email Verification Confirmation`; + const [updatedRows] = await User.update({ isVerified: true }, { where: { email: decodedToken.email } }); + await addToBlacklist(token); + if (updatedRows > 0) { + await sendEmailService(user,subject,generateEmailVerificationEmail(decodedToken.email)) + return { status: 200, message: 'User verified successfully.' }; + } else { + return { status: 404, message: 'User not found or already verified.' }; + } + } catch (error) { + throw new Error('Failed to verify user.'); + } +}; +export const verifyUserEmail = async (email: string) => { + try { + const user = await User.findOne({ where: { email} }); + if (!user) { + return { status: 404, message: 'User not found.' }; + } + const subject = 'Please verify your email address'; + const token = generateVerificationToken(email, 60); + const verificationLink = `${process.env.REMOTE_URL || `${process.env.LOCAL_URL}:${process.env.PORT}`}/api/v1/users/verify-user?token=${token}`; + + await sendEmailService(user,subject,verifyUserEmailTemplate(user.username,verificationLink)) + + return { status: 201, message: 'Verification email sent successfully.' }; + } catch (error) { + throw new Error('Failed to verify user.'); + } +}; \ No newline at end of file diff --git a/src/utils/generateResetToken.ts b/src/utils/generateResetToken.ts index a207042..3f9df07 100644 --- a/src/utils/generateResetToken.ts +++ b/src/utils/generateResetToken.ts @@ -11,6 +11,16 @@ export const generateResetToken = (email: string, expiresInMinutes: number): str }, `${env.jwt_secret}`); return token; }; +export const generateVerificationToken = (email: string, expiresInMinutes: number): string => { + const createdAt = Math.floor(Date.now() / 1000); + const expiresAt = createdAt + expiresInMinutes * 60; + const token = sign({ + email, + createdAt, + expiresAt + }, `${env.jwt_secret}`); + return token; +}; export const verifyResetToken = (token: string): { email: string } | null => { try {