Skip to content

Commit

Permalink
Forget and Reset password api design completed
Browse files Browse the repository at this point in the history
  • Loading branch information
Md-Rubel-Ahmed-Rana committed Jul 6, 2024
1 parent 5485089 commit b691372
Show file tree
Hide file tree
Showing 8 changed files with 307 additions and 0 deletions.
38 changes: 38 additions & 0 deletions backend/dist/controllers/user.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,44 @@ class Controller extends rootController_1.default {
data: null,
});
}));
this.forgetPassword = this.catchAsync((req, res) => __awaiter(this, void 0, void 0, function* () {
const { email } = req.body;
const result = yield user_service_1.UserService.forgetPassword(email);
if (!(result === null || result === void 0 ? void 0 : result.user)) {
this.apiResponse(res, {
success: false,
statusCode: http_status_1.default.NOT_FOUND,
message: "User not found",
data: null,
});
}
else if (result === null || result === void 0 ? void 0 : result.messageId) {
this.apiResponse(res, {
success: true,
statusCode: http_status_1.default.OK,
message: "Reset password link was send to your mail. Please check your inbox",
data: null,
});
}
else {
this.apiResponse(res, {
success: false,
statusCode: http_status_1.default.BAD_REQUEST,
message: "Something went wrong to send reset email",
data: null,
});
}
}));
this.resetPassword = this.catchAsync((req, res) => __awaiter(this, void 0, void 0, function* () {
const { userId, password } = req.body;
yield user_service_1.UserService.resetPassword(userId, password);
this.apiResponse(res, {
success: true,
statusCode: http_status_1.default.OK,
message: "Your password was changed",
data: null,
});
}));
this.logout = this.catchAsync((req, res) => __awaiter(this, void 0, void 0, function* () {
res.clearCookie("tmAccessToken", {
httpOnly: true,
Expand Down
2 changes: 2 additions & 0 deletions backend/dist/routes/user.route.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ router.get("/auth", auth_1.default, user_controller_1.UserController.auth);
router.patch("/update/:id", (0, validateRequest_1.default)(user_validation_1.UserValidationSchema.updateZodSchema), user_controller_1.UserController.updateUser);
router.post("/login", (0, validateRequest_1.default)(user_validation_1.UserValidationSchema.loginZodSchema), user_controller_1.UserController.login);
router.delete("/logout", user_controller_1.UserController.logout);
router.post("/forget-password", user_controller_1.UserController.forgetPassword);
router.post("/reset-password", user_controller_1.UserController.resetPassword);
exports.UserRoutes = router;
30 changes: 30 additions & 0 deletions backend/dist/services/user.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const mapper_1 = require("../mapper");
const user_entity_1 = require("@/entities/user.entity");
const get_1 = require("@/dto/user/get");
const update_1 = require("@/dto/user/update");
const mail_util_1 = require("@/utils/mail.util");
class Service {
getAllUsers() {
return __awaiter(this, void 0, void 0, function* () {
Expand Down Expand Up @@ -83,5 +84,34 @@ class Service {
return accessToken;
});
}
forgetPassword(email) {
return __awaiter(this, void 0, void 0, function* () {
const isUserExist = yield user_model_1.default.findOne({ email: email });
if (!isUserExist) {
return false;
}
else {
const jwtPayload = {
userId: isUserExist._id,
};
const token = jsonwebtoken_1.default.sign(jwtPayload, envConfig_1.config.jwt.accessTokenSecret, {
expiresIn: "10m",
});
const encodedEmail = encodeURIComponent(isUserExist.email);
const encodedName = encodeURIComponent(isUserExist.name);
const link = `${envConfig_1.config.app.frontendDomain}?token=${token}&userId=${isUserExist._id}&email=${encodedEmail}&name=${encodedName}`;
const mailResult = yield mail_util_1.MailUtilService.sendResetPasswordLink(email, link);
return { user: isUserExist, messageId: mailResult.messageId };
}
});
}
resetPassword(userId, password) {
return __awaiter(this, void 0, void 0, function* () {
const hashedPassword = yield bcrypt_1.default.hash(password, 12);
yield user_model_1.default.findByIdAndUpdate(userId, {
$set: { password: hashedPassword },
});
});
}
}
exports.UserService = new Service();
86 changes: 86 additions & 0 deletions backend/dist/utils/mail.util.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Expand Down Expand Up @@ -44,5 +53,82 @@ class Mail {
}
});
}
sendResetPasswordLink(to, link) {
return __awaiter(this, void 0, void 0, function* () {
const transporter = nodemailer_1.default.createTransport({
service: "gmail",
secure: true,
host: "smtp.gmail.com",
port: 465,
auth: {
user: envConfig_1.config.google.appUser,
pass: envConfig_1.config.google.appPass,
},
});
const mailOptions = {
from: `Team Manager <${envConfig_1.config.google.appUser}>`,
to: to,
subject: "Reset Password",
html: `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
font-family: Arial, sans-serif;
background-color: #f7f7f7;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.container {
background-color: #ffffff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 600px;
}
h1 {
color: #333333;
}
p {
color: #555555;
}
a {
display: inline-block;
margin-top: 20px;
padding: 10px 20px;
color: white;
background-color: #007BFF;
border-radius: 5px;
text-decoration: none;
}
a:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<div class="container">
<h1>Password Reset Request</h1>
<p>This link will be expired in 10 minutes</p>
<p>We received a request to reset your password. Click the link below to reset it:</p>
<a style="color: white" href="${link}">Reset Password</a>
<p>If you did not request a password reset, please ignore this email or contact support if you have questions.</p>
</div>
</body>
</html>
`,
};
const info = yield transporter.sendMail(mailOptions);
return info;
});
}
}
exports.MailUtilService = new Mail();
40 changes: 40 additions & 0 deletions backend/src/controllers/user.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,46 @@ class Controller extends RootController {
data: null,
});
});

forgetPassword = this.catchAsync(async (req: Request, res: Response) => {
const { email } = req.body;
const result: any = await UserService.forgetPassword(email);
if (!result?.user) {
this.apiResponse(res, {
success: false,
statusCode: httpStatus.NOT_FOUND,
message: "User not found",
data: null,
});
} else if (result?.messageId) {
this.apiResponse(res, {
success: true,
statusCode: httpStatus.OK,
message:
"Reset password link was send to your mail. Please check your inbox",
data: null,
});
} else {
this.apiResponse(res, {
success: false,
statusCode: httpStatus.BAD_REQUEST,
message: "Something went wrong to send reset email",
data: null,
});
}
});

resetPassword = this.catchAsync(async (req: Request, res: Response) => {
const { userId, password } = req.body;
await UserService.resetPassword(userId, password);
this.apiResponse(res, {
success: true,
statusCode: httpStatus.OK,
message: "Your password was changed",
data: null,
});
});

logout = this.catchAsync(async (req: Request, res: Response) => {
res.clearCookie("tmAccessToken", {
httpOnly: true,
Expand Down
4 changes: 4 additions & 0 deletions backend/src/routes/user.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,8 @@ router.post(

router.delete("/logout", UserController.logout);

router.post("/forget-password", UserController.forgetPassword);

router.post("/reset-password", UserController.resetPassword);

export const UserRoutes = router;
31 changes: 31 additions & 0 deletions backend/src/services/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { UserEntity } from "@/entities/user.entity";
import { GetUserDTO } from "@/dto/user/get";
import { ModelIdentifier } from "@automapper/core";
import { UpdateUserDTO } from "@/dto/user/update";
import { MailUtilService } from "@/utils/mail.util";

class Service {
async getAllUsers(): Promise<GetUserDTO[]> {
Expand Down Expand Up @@ -91,6 +92,36 @@ class Service {

return accessToken;
}

async forgetPassword(email: string) {
const isUserExist = await User.findOne({ email: email });

if (!isUserExist) {
return false;
} else {
const jwtPayload = {
userId: isUserExist._id,
};
const token = jwt.sign(jwtPayload, config.jwt.accessTokenSecret, {
expiresIn: "10m",
});
const encodedEmail = encodeURIComponent(isUserExist.email);
const encodedName = encodeURIComponent(isUserExist.name);
const link = `${config.app.frontendDomain}?token=${token}&userId=${isUserExist._id}&email=${encodedEmail}&name=${encodedName}`;
const mailResult = await MailUtilService.sendResetPasswordLink(
email,
link
);
return { user: isUserExist, messageId: mailResult.messageId };
}
}

async resetPassword(userId: string, password: string) {
const hashedPassword = await bcrypt.hash(password, 12);
await User.findByIdAndUpdate(userId, {
$set: { password: hashedPassword },
});
}
}

export const UserService = new Service();
76 changes: 76 additions & 0 deletions backend/src/utils/mail.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,82 @@ class Mail {
}
});
}
async sendResetPasswordLink(to: string, link: string) {
const transporter = nodemailer.createTransport({
service: "gmail",
secure: true,
host: "smtp.gmail.com",
port: 465,
auth: {
user: config.google.appUser,
pass: config.google.appPass,
},
});

const mailOptions = {
from: `Team Manager <${config.google.appUser}>`,
to: to,
subject: "Reset Password",
html: `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
font-family: Arial, sans-serif;
background-color: #f7f7f7;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.container {
background-color: #ffffff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 600px;
}
h1 {
color: #333333;
}
p {
color: #555555;
}
a {
display: inline-block;
margin-top: 20px;
padding: 10px 20px;
color: white;
background-color: #007BFF;
border-radius: 5px;
text-decoration: none;
}
a:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<div class="container">
<h1>Password Reset Request</h1>
<p>This link will be expired in 10 minutes</p>
<p>We received a request to reset your password. Click the link below to reset it:</p>
<a style="color: white" href="${link}">Reset Password</a>
<p>If you did not request a password reset, please ignore this email or contact support if you have questions.</p>
</div>
</body>
</html>
`,
};
const info = await transporter.sendMail(mailOptions);
return info;
}
}

export const MailUtilService = new Mail();

0 comments on commit b691372

Please sign in to comment.