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 23, 2024
1 parent c709966 commit bfff83d
Show file tree
Hide file tree
Showing 16 changed files with 352 additions and 13 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
29 changes: 26 additions & 3 deletions __test__/product.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: "[email protected]",
role:"seller",
password: "test1234",
Expand All @@ -27,6 +29,7 @@ const userData: any = {
const dummySeller = {
name: "dummy1234",
username: "username1234",
isVerified:true,
email: "[email protected]",
password: "1234567890",
};
Expand All @@ -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,
Expand All @@ -43,6 +47,7 @@ const product:any = {
const dummyBuyer = {
name: "test user",
username: "testUser",
isVerified:true,
email: "[email protected]",
password: "soleil00",
}
Expand All @@ -60,6 +65,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 All @@ -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) {
Expand All @@ -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 () =>{
Expand All @@ -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 () => {
Expand All @@ -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');

});

Expand Down Expand Up @@ -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')
})
Expand Down
40 changes: 37 additions & 3 deletions __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 @@ -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);
Expand Down Expand Up @@ -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;
});

Expand Down Expand Up @@ -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: "[email protected]",
password: "password"
password: "password"
})
adminToken = response.body.token;
});
Expand Down Expand Up @@ -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');
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"
}
}
};
Loading

0 comments on commit bfff83d

Please sign in to comment.