Create a Picture Competition Website in Express JS, MEVN

Forget Password

Forgot Password
Forgot Password

If a user forgets his password, then we must be able to reset it. He can do that by first providing his email address at which we will send a link to reset the password. When he clicks on that link from his email inbox, he will see a form where he can enter his new password twice for confirmation.

After he hit submits, the user password will be changed. So first we need to create a link on “login.ejs” that will take the user to the forget password page.

<p style="margin-top: 10px;">
	<a v-bind:href="baseUrl + '/forgotPassword'">Forgot password ?</a>
</p>

Show Forget Password Form

Then we need to create a GET route in “server.js” to display the forget password page.

app.get("/forgotPassword", function (request, result) {
	result.render("forgot-password");
});

Create a file named “forgot-password.ejs” inside the “views” folder and it will have the following code:

<%- include ("includes/header") %>

<div class="container margin-container" id="forgotPasswordApp">
	<div class="row">
		<div class="offset-md-3 col-md-6">

			<h2 class="text-center">Forgot Password</h2>

			<form method="POST" v-bind:action="baseUrl + '/sendRecoveryLink'" v-on:submit.prevent="sendRecoveryLink">

				<div class="form-group">
					<label>Email</label>
					<input type="email" name="email" class="form-control" required />
				</div>

				<input type="submit" name="submit" value="Send Recovery Link" class="btn btn-primary" />
			</form>
		</div>
	</div>
</div>

<script>
	var forgotPasswordApp = new Vue({
		el: "#forgotPasswordApp",
		data: {
			baseUrl: mainURL
		},
		methods: {
			sendRecoveryLink: async function () {
				const form = event.target;

				// disable the submit button and show "Loading..." text
		        form.submit.setAttribute("disabled", "disabled");
		        form.submit.value = "Loading...";

		        var formData = new FormData(form);
				myApp.callAjax(form.getAttribute("action"), formData, function (response) {
                    // convert the JSON string into Javascript object
                    var response = JSON.parse(response);

                    // enable the submit button
                    form.submit.removeAttribute("disabled");
                    form.submit.value = "Send Recovery Link";
 
                    // if the user is created, then redirect to login
                    if (response.status == "success") {
                    	swal("Success", response.message, "success");
                    } else if (response.status == "error") {
                    	swal("Error", response.message, "error");
                    }
				});
			}
		}
	});
</script>

<%- include ("includes/footer") %>

Refresh the page now and click on the “Forgot password ?” link on the login page. You will see a form to enter your email address. On submitting will call an AJAX to the server.

Sending Recovery Email

Now we need to create a POST route in “server.js” that will send an email with a link to reset the password.

app.post("/sendRecoveryLink", async function (request, result) {
	var email = request.fields.email;
	
	var user = await db.collection("users").findOne({
		"email": email
	});

	if (user == null) {
		result.json({
			"status": "error",
			"message": "Email does not exists."
		});
		return false;
	}

	var resetToken = new Date().getTime();
	await db.collection("users").findOneAndUpdate({
		"email": email
	}, {
		$set: {
			"resetToken": resetToken
		}
	});

	var transporter = nodemailer.createTransport(nodemailerObject);

	var html = "Please click the following link to reset your password: <br><br> <a href='" + mainURL + "/resetPassword/" + email + "/" + resetToken + "'>Reset Password</a> <br><br> Thank you.";

	transporter.sendMail({
		from: nodemailerFrom,
		to: email,
		subject: "Reset Password",
		text: html,
		html: html
	}, function (error, info) {
		if (error) {
			console.error(error);
		} else {
			console.log("Email sent: " + info.response);
		}
		
		result.json({
			"status": "success",
			"message": "Email has been sent with the link to recover the password."
		});
	});
});

Enter your email in the forget password form and hit submit. You will see a success message and you will see that the “resetToken” field in the “users” collection in Mongo DB will have some value. We will use this token to authenticate the request.

Show Password Reset Form

You will also see a new email in your inbox with a link. But if you click the link you will see a “404 not found” error. Because we need to create a GET route for this first. So create a GET route in “server.js” to display the reset password form:

app.get("/resetPassword/:email/:resetToken", function (request, result) {

	var email = request.params.email;
	var resetToken = request.params.resetToken;

	result.render("reset-password", {
		"email": email,
		"resetToken": resetToken
	});
});

Then you need to create a “reset-password.ejs” file inside the “views” folder to display a reset password form.

<%- include ("includes/header") %>

<div class="container margin-container" id="resetPasswordApp">
	<div class="row">
		<div class="offset-md-3 col-md-6">

			<h2 class="text-center">Reset Password</h2>

			<form method="POST" v-bind:action="baseUrl + '/resetPassword'" v-on:submit.prevent="resetPassword">

				<input type="hidden" name="reset_token" value="<%= resetToken %>">
                <input type="hidden" name="email" value="<%= email %>">

				<div class="form-group">
					<label>New Password</label>
                    <input type="password" name="new_password" class="form-control" required />
				</div>

                <div class="form-group">
                	<label>Confirm Password</label>
                    <input type="password" name="confirm_password" class="form-control" required />
				</div>

				<input type="submit" name="submit" value="Reset Password" class="btn btn-primary" />
			</form>
		</div>
	</div>
</div>

<script>
	var resetPasswordApp = new Vue({
		el: "#resetPasswordApp",
		data: {
			baseUrl: mainURL
		},
		methods: {
			resetPassword: async function () {
				const form = event.target;

				// disable the submit button and show "Loading..." text
		        form.submit.setAttribute("disabled", "disabled");
		        form.submit.value = "Loading...";

		        var formData = new FormData(form);
				myApp.callAjax(form.getAttribute("action"), formData, function (response) {
                    // convert the JSON string into Javascript object
                    var response = JSON.parse(response);

                    // enable the submit button
                    form.submit.removeAttribute("disabled");
                    form.submit.value = "Reset Password";
 
                    // if the user is created, then redirect to login
                    if (response.status == "success") {
                    	swal("Success", response.message, "success");
                    } else {
                    	swal("Error", response.message, "error");
                    }
				});
			}
		}
	});
</script>

<%- include ("includes/footer") %>

It will show a form with 2 password fields for confirmation. When submit will call an AJAX to the server.

Update the Password

So now we need to create a POST route in “server.js” that will update the user password.

app.post("/resetPassword", async function (request, result) {
    const email = request.fields.email;
    const resetToken = request.fields.reset_token;
    const newPassword = request.fields.new_password;
    const confirmPassword = request.fields.confirm_password;

    if (newPassword != confirmPassword) {
    	result.json({
			"status": "error",
			"message": "Password does not match."
		});
        return;
    }

    var user = await db.collection("users").findOne({
		$and: [{
			"email": email,
		}, {
			"resetToken": parseInt(resetToken)
		}]
	});

	if (user == null) {
		result.json({
			"status": "error",
			"message": "Email does not exists. Or recovery link is expired."
		});
		return false;
	}

	bcrypt.hash(newPassword, 10, async function (error, hash) {
		await db.collection("users").findOneAndUpdate({
			$and: [{
				"email": email,
			}, {
				"resetToken": parseInt(resetToken)
			}]
		}, {
			$set: {
				"resetToken": "",
				"password": hash
			}
		});

		result.json({
			"status": "success",
			"message": "Password has been changed. Please try login again."
		});
	});
});

You can see that we are also encrypting the password before updating it in Mongo DB. Now you can easily reset the password if you ever forget it.