Skip to content

Commit

Permalink
Ft: auth (new user needs to verify one's account before login
Browse files Browse the repository at this point in the history
  • Loading branch information
MugemaneBertin2001 committed May 22, 2024
1 parent c709966 commit f087adc
Show file tree
Hide file tree
Showing 16 changed files with 322 additions and 8 deletions.
3 changes: 3 additions & 0 deletions __test__/cart.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,22 @@ describe("testing cart", () => {
name: "admin123",
username: "admin123",
email: "[email protected]",
isVerified:true,
password: await bcrypt.hash("password", 10),
roleId: 3,
};

const testBuyer = {
name: "buyer123",
username: "buyer123",
isVerified:true,
email: "[email protected]",
password: await bcrypt.hash("password", 10),
};
const testSeller = {
name: "seller123",
username: "seller123",
isVerified:true,
email: "[email protected]",
password: await bcrypt.hash("password", 10),
};
Expand Down
3 changes: 3 additions & 0 deletions __test__/payment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ describe("test stripe api payment", () => {
const testAdmin = {
name: "admin123",
username: "admin123",
isVerified:true,
email: "[email protected]",
password: await bcrypt.hash("password", 10),
roleId: 3,
Expand All @@ -30,13 +31,15 @@ describe("test stripe api payment", () => {
const testBuyer = {
name: "buyer123",
username: "buyer123",
isVerified:true,
email: "[email protected]",
password: await bcrypt.hash("password", 10),
};

const testSeller = {
name: "seller123",
username: "seller123",
isVerified:true,
email: "[email protected]",
password: await bcrypt.hash("password", 10),
};
Expand Down
5 changes: 5 additions & 0 deletions __test__/product.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import OrderItem from "../src/sequelize/models/orderItems";
const userData: any = {
name: "yvanna",
username: "testuser",
isVerified:true,
email: "[email protected]",
role:"seller",
password: "test1234",
Expand All @@ -27,6 +28,7 @@ const userData: any = {
const dummySeller = {
name: "dummy1234",
username: "username1234",
isVerified:true,
email: "[email protected]",
password: "1234567890",
};
Expand All @@ -35,6 +37,7 @@ const product:any = {
name: "pens",
images: ["image1.jpg", "image2.jpg", "image3.jpg", "image4.jpg"],
stockQuantity: 8,

price: 5000,
discount: 3.5,
categoryID: 1,
Expand All @@ -43,6 +46,7 @@ const product:any = {
const dummyBuyer = {
name: "test user",
username: "testUser",
isVerified:true,
email: "[email protected]",
password: "soleil00",
}
Expand All @@ -60,6 +64,7 @@ describe("Testing product Routes", () => {
await connect();
const testAdmin = {
name: "admin123",
isVerified:true,
username: "admin123",
email: "[email protected]",
password: await bcrypt.hash("password", 10),
Expand Down
29 changes: 28 additions & 1 deletion __test__/user.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ 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;


const userData: any = {
name: "yvanna5",
username: "testuser5",
isVerified:true,
email: "[email protected]",
password: "test12345",
};
Expand All @@ -31,11 +32,13 @@ const userData: any = {
const dummySeller = {
name: "dummy1234",
username: "username1234",
isVerified:true,
email: "[email protected]",
password: "1234567890",
};
const userTestData = {
newPassword: "Test@123",
isVerified:true,
confirmPassword: "Test@123",
wrongPassword: "Test456",
};
Expand Down Expand Up @@ -548,6 +551,30 @@ 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}`)
console.log(response.status)
console.log(response.body.message)
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');
Expand Down
30 changes: 29 additions & 1 deletion src/controllers/userControllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand Down Expand Up @@ -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
});
Expand Down Expand Up @@ -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<void> => {
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.' });
}
};
5 changes: 4 additions & 1 deletion src/docs/swagger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
sendResetLink,
updateForgotPassword,
verifyUserAccessToken,
verifyUserEmail,
} from "./users";
import {
getProducts,
Expand All @@ -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";
Expand Down Expand Up @@ -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,
Expand Down
36 changes: 36 additions & 0 deletions src/docs/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
};
95 changes: 95 additions & 0 deletions src/email-templates/verifyUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// emailTemplates/verifyUser.ts
const verifyUserEmailTemplate = (username:string,verificationLink:string) => {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Verify Your Email</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
color: #333333;
}
.container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
background-color: #ffffff;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.header {
text-align: center;
padding-bottom: 20px;
}
.header img {
max-width: 100px;
}
.content {
font-size: 16px;
line-height: 1.5;
}
.btn {
display: inline-block;
margin-top: 20px;
padding: 10px 20px;
background-color: #007bff;
color: #ffffff;
text-decoration: none;
border-radius: 4px;
}
.footer {
margin-top: 20px;
font-size: 12px;
color: #666666;
text-align: center;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<img src="https://yourcompanylogo.com/logo.png" alt="Company Logo">
</div>
<div class="content">
<h1>Welcome, ${username}!</h1>
<p>Thank you for registering with us. Please verify your email address to complete your registration.</p>
<p>Click the button below to verify your email address:</p>
<a href="${verificationLink}" class="btn">Verify Email</a>
<p>If the button above does not work, copy and paste the following link into your browser:</p>
<p><a href="${verificationLink}">${verificationLink}</a></p>
</div>
<div class="footer">
<p>&copy; 2024 Your Company Name. All rights reserved.</p>
<p>If you did not register for this account, please ignore this email.</p>
</div>
</div>
</body>
</html>
`;


};

export default verifyUserEmailTemplate;


export const generateEmailVerificationEmail = (Username:string)=> {
return `
Dear ${Username},
<br />
Thank you for verifying your email address. You are now able to start using our system.
<br />
If you have any questions, feel free to reach out to our support team.
<br />
Best regards,
<br />
ATLP-eccommerce
`;
}
16 changes: 16 additions & 0 deletions src/middlewares/isVerified.ts
Original file line number Diff line number Diff line change
@@ -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();
};
Loading

0 comments on commit f087adc

Please sign in to comment.