Implement password reset and change features with email notifications

This commit is contained in:
Isuru Bimsara
2026-08-21 09:56:54 +05:30
parent b2c5e198a1
commit 6a4fe852e5
6 changed files with 597 additions and 138 deletions
+211 -1
View File
@@ -9,8 +9,12 @@
// app/controllers/auth.controller.js
const { checkPassword } = require("../utils/hashPassword.util");
const { checkPassword, hashPassword } = require("../utils/hashPassword.util");
const { sendMail } = require("../utils/mail.util");
const {
validatePassword,
} = require("../utils/validation/validatePassword.util");
const { validateEmail } = require("../utils/validation/validateEmail.util");
const { generateOTP, validateOTP } = require("../utils/otp.util");
const { getCachedUser, clearUserCache } = require("../utils/cache.util");
const { generateToken } = require("../utils/jwt.util");
@@ -18,7 +22,15 @@ const {
createRefreshSession,
validateRefreshSession,
deleteRefreshSession,
deleteAllUserSessions,
} = require("../utils/refreshSession.util");
const {
createPasswordReset,
verifyPasswordResetToken,
deletePasswordReset,
sendPasswordResetEmail,
sendPasswordChangedEmail
} = require("../utils/passwordReset.utill");
const db = require("../models");
const { log } = require("../utils/consoleLog.utill");
@@ -227,6 +239,204 @@ exports.refreshToken = async (req, res) => {
}
};
exports.forgotPassword = async (req, res) => {
try {
let { email } = req.body;
if (!email) {
return res.status(400).send({
success: false,
message: "Email is required",
});
}
email = email.trim().toLowerCase();
if (!validateEmail(email)) {
return res.status(400).send({
success: false,
message: "Invalid email address",
});
}
const user = await User.findOne({
where: { email },
});
if (!user) {
return res.status(200).send({
success: true,
message:
"If an account exists for this email, a password reset link has been sent.",
});
}
const resetToken = await createPasswordReset(user.id);
await sendPasswordResetEmail(user.email, user.firstName, resetToken);
return res.status(200).send({
success: true,
message:
"If an account exists for this email, a password reset link has been sent.",
});
} catch (error) {
console.error("FORGOT PASSWORD ERROR:", error);
return res.status(500).send({
success: false,
message: "Unable to process password reset request",
});
}
};
exports.resetPassword = async (req, res) => {
try {
const {
token,
newPassword,
confirmPassword,
} = req.body;
if (!token ||!newPassword || !confirmPassword) {
return res.status(400).send({
success: false,
message:
"Token, new password and confirm password are required",
});
}
if (
newPassword !==
confirmPassword
) {
return res.status(400).send({
success: false,
message:
"Passwords do not match",
});
}
if (
!validatePassword(newPassword)
) {
return res.status(400).send({
success: false,
message:
"Password does not meet the required criteria",
});
}
const verification =
await verifyPasswordResetToken(
token
);
if (!verification) {
return res.status(400).send({
success: false,
message:
"Reset token is invalid or expired",
});
}
const {
userId,
redisKey,
} = verification;
const user =
await User.findByPk(userId);
if (!user) {
await deletePasswordReset(
redisKey
);
return res.status(400).send({
success: false,
message:
"Reset token is invalid or expired",
});
}
const samePassword =
await checkPassword(
newPassword,
user.password
);
if (samePassword) {
return res.status(400).send({
success: false,
message:
"New password must be different from the current password",
});
}
const hashedPassword =
await hashPassword(
newPassword
);
user.password =
hashedPassword;
user.passwordChangedAt =
new Date();
await user.save();
await sendPasswordChangedEmail(
user.email,
user.firstName
);
await deletePasswordReset(
redisKey
);
deleteAllUserSessions(
user.id
);
return res.status(200).send({
success: true,
message:
"Password reset successfully. Please login again.",
});
} catch (error) {
console.error(
"RESET PASSWORD ERROR:",
error
);
return res.status(500).send({
success: false,
message:
"Failed to reset password",
});
}
};
// Logout: Clear the JWT cookie
exports.logout = (req, res) => {
res.clearCookie("access_token", {