-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ac06e84
commit 8f69fbb
Showing
8 changed files
with
275 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -95,23 +95,118 @@ describe('POST /user/verify/:id', () => { | |
const userRepository = getRepository(User); | ||
const user = await userRepository.findOne({ where: { email: newUser.email } }); | ||
|
||
if(user){ | ||
if (user) { | ||
const verifyRes = await request(app).get(`/user/verify/${user.id}`); | ||
|
||
// Assert | ||
expect(verifyRes.status).toBe(200); | ||
expect(verifyRes.text).toEqual('<p>User verified successfully</p>'); | ||
|
||
// Check that the user's verified field is now true | ||
const verifiedUser = await userRepository.findOne({ where: { email: newUser.email } }); | ||
if (verifiedUser){ | ||
if (verifiedUser) { | ||
expect(verifiedUser.verified).toBe(true); | ||
} | ||
|
||
} | ||
|
||
if (user) { | ||
await userRepository.remove(user); | ||
} | ||
}); | ||
}); | ||
}); | ||
|
||
describe('Send password reset link', () => { | ||
|
||
it('Attempt to send email with rate limiting', async () => { | ||
const email = "[email protected]"; | ||
|
||
const requests = Array.from({ length: 5 }, async () => { | ||
return await request(app).post(`/user/password/reset/link?email=${email}`); | ||
}); | ||
|
||
const responses = await Promise.all(requests); | ||
const lastResponse = responses[responses.length - 1]; | ||
expect(lastResponse.status).toBe(500); | ||
expect(lastResponse.body.message).toEqual('User not found'); | ||
}); | ||
|
||
it('Attempt to send email with invalid email template', async () => { | ||
const email = "[email protected]"; | ||
|
||
const res = await request(app).post(`/user/password/reset/link?email=${email}`); | ||
|
||
expect(res.status).toBe(500); | ||
expect(res.body.message).toEqual('User not found'); | ||
}); | ||
|
||
it('Send email to a user with special characters in email address', async () => { | ||
const email = "[email protected]"; | ||
|
||
const res = await request(app).post(`/user/password/reset/link?email=${encodeURIComponent(email)}`); | ||
|
||
expect(res.status).toBe(500); | ||
expect(res.body.message).toEqual('User not found'); | ||
}); | ||
|
||
}); | ||
describe('Password Reset Service', () => { | ||
it('Should reset password successfully', async () => { | ||
const data = { | ||
"newPassword": "user", | ||
"confirmPassword": "user", | ||
}; | ||
const email = "[email protected]"; | ||
const userRepository = getRepository(User); | ||
const user = await userRepository.findOne({ where: { email: email } }); | ||
if (user) { | ||
const res: any = await request(app).post(`/user/password/reset?userid=${user.id}&email=${email}`).send(data); | ||
// Assert | ||
expect(res.status).toBe(200); | ||
expect(res.data.message).toEqual('Password updated successful'); | ||
} | ||
}); | ||
|
||
it('Should return 404 if user not found', async () => { | ||
const data = { | ||
"newPassword": "user", | ||
"confirmPassword": "user", | ||
}; | ||
const email = "[email protected]"; | ||
const userId = "nonexistentuserid"; | ||
const res: any = await request(app).post(`/user/password/reset?userid=${userId}&email=${email}`).send(data); | ||
// Assert | ||
expect(res.status).toBe(404); | ||
}); | ||
|
||
it('Should return 204 if required fields are missing', async () => { | ||
const data = { | ||
// | ||
}; | ||
const email = "[email protected]"; | ||
|
||
const userRepository = getRepository(User); | ||
const user = await userRepository.findOne({ where: { email: email } }); | ||
if (user) { | ||
const res: any = await request(app).post(`/user/password/reset?userid=${user.id}&email=${email}`).send(data); | ||
expect(res.status).toBe(204); | ||
expect(res.data.error).toEqual('Please provide all required fields'); | ||
} | ||
}); | ||
|
||
it('Should return 204 if newPassword and confirmPassword do not match', async () => { | ||
const data = { | ||
"newPassword": "user123", | ||
"confirmPassword": "user456", | ||
}; | ||
const email = "[email protected]"; | ||
|
||
const userRepository = getRepository(User); | ||
const user = await userRepository.findOne({ where: { email: email } }); | ||
if (user) { | ||
const res: any = await request(app).post(`/user/password/reset?userid=${user.id}&email=${email}`).send(data); | ||
expect(res.status).toBe(204); | ||
expect(res.data.error).toEqual('New password must match confirm password'); | ||
} | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,2 @@ | ||
import { userRegistration,userVerification } from './authController'; | ||
|
||
export { userRegistration,userVerification }; | ||
export * from './authController'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
// export all middlewares | ||
export * from "./errorHandler" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,14 @@ | ||
import { Router } from 'express'; | ||
import { userRegistration, userVerification} from '../controllers/index'; | ||
import { sendPasswordResetLink, userPasswordReset , userRegistration, userVerification} from '../controllers'; | ||
|
||
|
||
|
||
|
||
const router = Router(); | ||
|
||
router.post('/register', userRegistration); | ||
router.get('/verify/:id', userVerification); | ||
router.post("/password/reset", userPasswordReset); | ||
router.post("/password/reset/link", sendPasswordResetLink); | ||
|
||
export default router; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
// export all Services | ||
export * from './userServices/userRegistrationService'; | ||
export * from './userServices/userValidationService'; | ||
|
||
export * from "./userServices/sendResetPasswordLinkService"; | ||
export * from "./userServices/userPasswordResetService"; | ||
export * from "./userServices/userRegistrationService"; | ||
export * from "./userServices/userValidationService"; |
120 changes: 120 additions & 0 deletions
120
src/services/userServices/sendResetPasswordLinkService.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
import { Request, Response } from "express"; | ||
import { responseError, responseServerError, responseSuccess } from "../../utils/response.utils"; | ||
import nodemailer from 'nodemailer'; | ||
import { getRepository } from "typeorm"; | ||
import { User } from "../../entities/User"; | ||
|
||
export const sendPasswordResetLinkService = async (req: Request, res: Response) => { | ||
try { | ||
const transporter = nodemailer.createTransport({ | ||
host: process.env.HOST, | ||
port: 587, | ||
secure: false, // true for 465, false for other ports | ||
auth: { | ||
user: process.env.AUTH_EMAIL, | ||
pass: process.env.AUTH_PASSWORD | ||
}, | ||
}); | ||
const email = req.query.email as string; | ||
|
||
if (!email) { | ||
return responseError(res, 500, 'Missing required field'); | ||
} | ||
const userRepository = getRepository(User); | ||
const existingUser = await userRepository.findOneBy({ email }); | ||
if (!existingUser) { | ||
return responseError(res, 500, 'User not found', existingUser); | ||
} | ||
const mailOptions: nodemailer.SendMailOptions = { | ||
to: email, | ||
subject: `Password reset link `, | ||
html: ` | ||
<!doctype html> | ||
<html lang="en-US"> | ||
<head> | ||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" /> | ||
<title>Reset Password Email Template</title> | ||
<meta name="description" content="Reset Password Email Template."> | ||
<style type="text/css"> | ||
a:hover {text-decoration: underline !important;} | ||
</style> | ||
</head> | ||
<body marginheight="0" topmargin="0" marginwidth="0" style="margin: 0px; background-color: #f2f3f8;" leftmargin="0"> | ||
<!--100% body table--> | ||
<table cellspacing="0" border="0" cellpadding="0" width="100%" bgcolor="#f2f3f8" | ||
style="@import url(https://fonts.googleapis.com/css?family=Rubik:300,400,500,700|Open+Sans:300,400,600,700); font-family: 'Open Sans', sans-serif;"> | ||
<tr> | ||
<td> | ||
<table style="background-color: #f2f3f8; max-width:670px; margin:0 auto;" width="100%" border="0" | ||
align="center" cellpadding="0" cellspacing="0"> | ||
<tr> | ||
<td style="height:80px;"> </td> | ||
</tr> | ||
<tr> | ||
<td style="height:20px;"> </td> | ||
</tr> | ||
<tr> | ||
<td> | ||
<table width="95%" border="0" align="center" cellpadding="0" cellspacing="0" | ||
style="max-width:670px;background:#fff; border-radius:3px; text-align:center;-webkit-box-shadow:0 6px 18px 0 rgba(0,0,0,.06);-moz-box-shadow:0 6px 18px 0 rgba(0,0,0,.06);box-shadow:0 6px 18px 0 rgba(0,0,0,.06);"> | ||
<tr> | ||
<td style="height:40px;"> </td> | ||
</tr> | ||
<tr> | ||
<td style="padding:0 35px;"> | ||
<h1 style="color:#1e1e2d; font-weight:500; margin:0;font-size:32px;font-family:'Rubik',sans-serif;text-decoration:none; ">You have | ||
requested to reset your password</h1> | ||
<span | ||
style="display:inline-block; vertical-align:middle; margin:29px 0 26px; border-bottom:1px solid #cecece; width:100px;"></span> | ||
<p style="color:#455056; font-size:15px;line-height:24px; margin:0;"> | ||
We cannot simply send you your old password. A unique link to reset your | ||
password has been generated for you. To reset your password, click the | ||
following link and follow the instructions. | ||
</p> | ||
<a href="${process.env.FRONTEND_URL}/${process.env.PASSWORD_ROUTE}?userid=${existingUser.id}&email=${existingUser.email}" target="_blank" | ||
style="background:#20e277;text-decoration:none !important; font-weight:500; margin-top:35px; color:#fff;text-transform:uppercase; font-size:14px;padding:10px 24px;display:inline-block;border-radius:50px;">Reset | ||
Password</a> | ||
</td> | ||
</tr> | ||
<tr> | ||
<td style="height:40px;"> </td> | ||
</tr> | ||
</table> | ||
</td> | ||
<tr> | ||
<td style="height:20px;"> </td> | ||
</tr> | ||
<tr> | ||
<td style="text-align:center;"> | ||
<p style="font-size:14px; color:rgba(69, 80, 86, 0.7411764705882353); line-height:18px; margin:0 0 0;">© <strong>Knights Ecommerce</strong></p> | ||
</td> | ||
</tr> | ||
<tr> | ||
<td style="height:80px;"> </td> | ||
</tr> | ||
</table> | ||
</td> | ||
</tr> | ||
</table> | ||
<!--/100% body table--> | ||
</body> | ||
</html>` | ||
}; | ||
|
||
try { | ||
const sendMail = await transporter.sendMail(mailOptions); | ||
return responseSuccess(res, 200, "Code sent on your email", sendMail); | ||
} catch (error) {; | ||
return responseError(res, 500, 'Error occurred while sending email'); | ||
} | ||
|
||
|
||
} catch (error) { | ||
return responseServerError(res, `Internal server error: `); | ||
} | ||
} | ||
; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import bcrypt from 'bcrypt'; | ||
import { Request, Response } from "express"; | ||
import { responseError, responseServerError, responseSuccess } from "../../utils/response.utils"; | ||
import { getRepository } from "typeorm"; | ||
import { User } from "../../entities/User"; | ||
|
||
export const userPasswordResetService = async (req: Request, res: Response) => { | ||
try { | ||
const { email, userid } = req.params; | ||
const { newPassword, confirmPassword } = req.body; | ||
|
||
const userRepository = getRepository(User); | ||
|
||
const existingUser = await userRepository.findOneBy({ email, id: userid }); | ||
if (!existingUser) { | ||
return responseError(res, 404, 'Something went wrong in finding your data'); | ||
} | ||
|
||
if (!newPassword || !confirmPassword) { | ||
return responseError(res, 204, 'Please provide all required fields'); | ||
} | ||
if (newPassword !== confirmPassword) { | ||
return responseError(res, 204, 'new password must match confirm password'); | ||
} | ||
const saltRounds = 10; | ||
const hashedPassword = await bcrypt.hash(newPassword, saltRounds); | ||
|
||
existingUser.password = hashedPassword; | ||
const updadeUser = await userRepository.save(existingUser); | ||
return responseSuccess(res, 200, "Password updated successful", updadeUser); | ||
} catch (error) { | ||
return responseServerError(res, "Internal server error"); | ||
} | ||
} | ||
|