diff --git a/backend/dist/controllers/user.controller.js b/backend/dist/controllers/user.controller.js index e5e96df..2781673 100644 --- a/backend/dist/controllers/user.controller.js +++ b/backend/dist/controllers/user.controller.js @@ -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, diff --git a/backend/dist/routes/user.route.js b/backend/dist/routes/user.route.js index d460e88..edf0d4b 100644 --- a/backend/dist/routes/user.route.js +++ b/backend/dist/routes/user.route.js @@ -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; diff --git a/backend/dist/services/user.service.js b/backend/dist/services/user.service.js index 9d0cdc5..a4abdaf 100644 --- a/backend/dist/services/user.service.js +++ b/backend/dist/services/user.service.js @@ -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* () { @@ -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(); diff --git a/backend/dist/utils/mail.util.js b/backend/dist/utils/mail.util.js index 9192e97..5fb0e01 100644 --- a/backend/dist/utils/mail.util.js +++ b/backend/dist/utils/mail.util.js @@ -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 }; }; @@ -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: ` + + + + + + + + +
+

Password Reset Request

+

This link will be expired in 10 minutes

+

We received a request to reset your password. Click the link below to reset it:

+ Reset Password +

If you did not request a password reset, please ignore this email or contact support if you have questions.

+
+ + + `, + }; + const info = yield transporter.sendMail(mailOptions); + return info; + }); + } } exports.MailUtilService = new Mail(); diff --git a/backend/src/controllers/user.controller.ts b/backend/src/controllers/user.controller.ts index fa103ea..1fc9cb5 100644 --- a/backend/src/controllers/user.controller.ts +++ b/backend/src/controllers/user.controller.ts @@ -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, diff --git a/backend/src/routes/user.route.ts b/backend/src/routes/user.route.ts index 800bd9d..16d1c36 100644 --- a/backend/src/routes/user.route.ts +++ b/backend/src/routes/user.route.ts @@ -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; diff --git a/backend/src/services/user.service.ts b/backend/src/services/user.service.ts index 83b1b1b..bd9785c 100644 --- a/backend/src/services/user.service.ts +++ b/backend/src/services/user.service.ts @@ -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 { @@ -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(); diff --git a/backend/src/utils/mail.util.ts b/backend/src/utils/mail.util.ts index 8a6c3d7..473367a 100644 --- a/backend/src/utils/mail.util.ts +++ b/backend/src/utils/mail.util.ts @@ -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: ` + + + + + + + + +
+

Password Reset Request

+

This link will be expired in 10 minutes

+

We received a request to reset your password. Click the link below to reset it:

+ Reset Password +

If you did not request a password reset, please ignore this email or contact support if you have questions.

+
+ + + `, + }; + const info = await transporter.sendMail(mailOptions); + return info; + } } export const MailUtilService = new Mail();