Scenario : When I create/register user1 ,the verification mail is sent to that email id and he(user1) is being verified successfully and I am able to change password for user1. After creating user1 , I am creating/registering user2 ,where the verification email is sent to the account .After clicking the link , it's becomes INVALID Overall , I am only able to create one user
Languages used : MERN stack
Backend => route.js :
const express = require("express");
const router = express.Router();
const User = require("../models/userModel");
const Doctor = require("../models/doctorModel");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const authMiddleware = require("../middlewares/authMiddleware");
const sendEmail = require("../utils/sendMail");
const Token = require("../models/tokenModel");
const Appointment = require("../models/appointmentModel");
const moment = require("moment");
router.post("/register", async (req, res) => {
try {
const userExists = await User.findOne({ email: req.body.email });
if (userExists) {
return res
.status(200)
.send({ message: "User already exists", success: false });
}
const password = req.body.password;
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(password, salt);
req.body.password = hashedPassword;
const newuser = new User(req.body);
const result = await newuser.save();
await sendEmail(result, "verifyemail");
res
.status(200)
.send({ message: "User created successfully", success: true });
} catch (error) {
console.log(error);
res
.status(500)
.send({ message: "Error creating user", success: false, error });
}
});
router.post("/login", async (req, res) => {
try {
const result = await User.findOne({ data: req.body.userId });
console.log(result);
const user = await User.findOne({ email: req.body.email });
if (!user) {
return res
.status(200)
.send({ message: "User does not exist", success: false });
}
if (user.isVerified === false) {
return res
.status(200)
.send({ message: "User not Verified", success: false });
}
const isMatch = await bcrypt.compare(req.body.password, user.password);
if (!isMatch) {
return res
.status(200)
.send({ message: "Password is incorrect", success: false });
} else {
const dataToBeSentToFrontend = {
id: user._id,
email: user.email,
name: user.name,
};
const token = jwt.sign(dataToBeSentToFrontend, process.env.JWT_SECRET, {
expiresIn: "1d",
});
res
.status(200)
.send({ message: "Login successful", success: true, data: token });
}
} catch (error) {
console.log(error);
res
.status(500)
.send({ message: "Error logging in", success: false, error });
}
});
router.post("/get-user-info-by-id", authMiddleware, async (req, res) => {
try {
const user = await User.findOne({ _id: req.body.userId });
user.password = undefined;
if (!user) {
return res
.status(200)
.send({ message: "User does not exist", success: false });
} else {
res.status(200).send({
success: true,
data: user,
});
}
} catch (error) {
res
.status(500)
.send({ message: "Error getting user info", success: false, error });
}
});
router.post("/send-password-reset-link", async (req, res) => {
try {
const result = await User.findOne({ email: req.body.email });
await sendEmail(result, "resetpassword");
res.send({
success: true,
message: "Password reset link sent to your email successfully",
});
} catch (error) {
res.status(500).send(error);
}
});
router.post("/resetpassword", async (req, res) => {
try {
const tokenData = await Token.findOne({ token: req.body.token });
if (tokenData) {
const password = req.body.password;
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(password, salt);
await User.findOneAndUpdate({
_id: tokenData.userid,
password: hashedPassword,
});
await Token.findOneAndDelete({ token: req.body.token });
res.send({ success: true, message: "Password reset successfull" });
} else {
res.send({ success: false, message: "Invalid token" });
}
} catch (error) {
res.status(500).send(error);
}
});
router.post("/verifyemail", async (req, res) => {
try {
const tokenData = await Token.findOne({ token: req.body.token });
if (tokenData) {
await User.findOneAndUpdate({ _id: tokenData.userid, isVerified: true });
await Token.findOneAndDelete({ token: req.body.token });
res.send({ success: true, message: "Email Verified Successlly" });
} else {
res.send({ success: false, message: "Invalid token" });
}
} catch (error) {
res.status(500).send(error);
}
});
Backend => sendEmail.js :
const nodemailer = require("nodemailer");
const bcrypt = require("bcrypt");
const Token = require("../models/tokenModel");
module.exports = async (user, mailType) => {
try {
const transporter = nodemailer.createTransport({
service: "gmail",
host: "smtp.gmail.com",
port: 587,
secure: true,
auth: {
user: "sh***********th#gmail.com",
pass: "e**************l",
},
});
const encryptedToken = bcrypt
.hashSync(user._id.toString(), 10)
.replaceAll("/", "");
const token = new Token({
userid: user._id,
token: encryptedToken,
});
await token.save();
let mailOptions, emailContent;
if (mailType === "verifyemail") {
emailContent = `<div><h1>Please click on the below link to verify your email address</h1> ${encryptedToken} </div>`;
mailOptions = {
from: "sh************th#gmail.com",
to: user.email,
subject: "Verify Email For MERN Auth",
html: emailContent,
};
} else {
emailContent = `<div><h1>Please click on the below link to reset your password</h1> ${encryptedToken} </div>`;
mailOptions = {
from: "shanshangeeth#gmail.com",
to: user.email,
subject: "Reset Password",
html: emailContent,
};
}
await transporter.sendMail(mailOptions);
} catch (error) {
console.log(error);
}
};
Backend => authMiddleware.js :
const jwt = require("jsonwebtoken");
module.exports = async (req, res, next) => {
try {
const token = req.headers["authorization"].split(" ")[1];
jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
if (err) {
return res.status(401).send({
message: "Auth failed",
success: false,
});
} else {
req.body.userId = decoded.id;
next();
}
});
} catch (error) {
return res.status(401).send({
message: "Auth failed",
success: false,
});
}
};
Backend => tokenmodel.js :
const mongoose = require("mongoose");
const tokenSchema = new mongoose.Schema(
{
userid: {
type: String,
required: true,
},
token: {
type: String,
required: true,
},
},
{ timestamps: true }
);
const tokenModel = mongoose.model("tokens", tokenSchema);
module.exports = tokenModel;
Backend => userModel.js
const mongoose = require("mongoose");
const userSchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
isVerified: {
type: Boolean,
default: false,
},
isDoctor: {
type: Boolean,
default: false,
},
isDoctor: {
type: Boolean,
default: false,
},
isAdmin: {
type: Boolean,
default: false,
},
seenNotifications: {
type: Array,
default: [],
},
unseenNotifications: {
type: Array,
default: [],
},
},
{
timestamps: true,
}
);
const userModel = mongoose.model("users", userSchema);
module.exports = userModel;
Frontend => VerifyEmail.js
path="/verifyemail/:token" in app.js inside PublicRoute
import React, { useState, useEffect } from "react";
import { useParams } from "react-router-dom";
import { toast } from "react-hot-toast";
import axios from "axios";
function VerifyEmail() {
const [emailVerified, setEmailVerified] = useState("");
const params = useParams();
const verifyToken = async () => {
try {
toast.loading();
const response = await axios.post("/api/user/verifyemail", {
token: params.token,
});
if (response.data.success) {
setEmailVerified("true");
} else {
setEmailVerified("false");
}
toast.dismiss();
} catch (error) {
toast.dismiss();
setEmailVerified("false");
}
};
useEffect(() => {
verifyToken();
}, []);
console.log(emailVerified);
return (
<div className="flex min-h-screen p-5 justify-center items-center">
{emailVerified == "" && (
<h1 className="text-primary text-4xl">
Please wait we are verifying your email
</h1>
)}
{emailVerified == "true" && (
<h1 className="text-primary text-4xl">
Your email verified successfully
</h1>
)}
{emailVerified == "false" && (
<h1 className="text-primary text-4xl">
Invalid or Expired Token
{console.log(emailVerified)}
</h1>
)}
</div>
);
}
export default VerifyEmail;
Frontend =>ProtectedRoute.js
import React from "react";
import { Navigate, useNavigate } from "react-router-dom";
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import axios from "axios";
import { setUser } from "../redux/userSlice";
import { showLoading, hideLoading } from "../redux/alertsSlice";
function ProtectedRoute(props) {
const { user } = useSelector((state) => state.user);
const dispatch = useDispatch();
const navigate = useNavigate();
const getUser = async () => {
try {
dispatch(showLoading());
const response = await axios.post(
"/api/user/get-user-info-by-id",
{ token: localStorage.getItem("token") },
{
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
}
);
console.log('TOKEN ("token") : ', localStorage.getItem("token"));
dispatch(hideLoading());
if (response.data.success) {
dispatch(setUser(response.data.data));
console.log(response.data.data);
} else {
localStorage.clear();
navigate("/login");
}
} catch (error) {
dispatch(hideLoading());
localStorage.clear();
navigate("/login");
}
};
useEffect(() => {
if (!user) {
getUser();
}
}, [user]);
if (localStorage.getItem("token")) {
return props.children;
} else {
return <Navigate to="/login" />;
}
}
export default ProtectedRoute;
Frontend=> VerifyEmail.js
import React, { useState, useEffect } from "react";
import { useParams } from "react-router-dom";
import { toast } from "react-hot-toast";
import axios from "axios";
function VerifyEmail() {
const [emailVerified, setEmailVerified] = useState("");
const params = useParams();
const verifyToken = async () => {
try {
toast.loading();
const response = await axios.post("/api/user/verifyemail", {
token: params.token,
});
if (response.data.success) {
setEmailVerified("true");
} else {
setEmailVerified("false");
}
toast.dismiss();
} catch (error) {
toast.dismiss();
setEmailVerified("false");
}
};
useEffect(() => {
verifyToken();
}, []);
console.log(emailVerified);
return (
<div className="flex min-h-screen p-5 justify-center items-center">
{emailVerified == "" && (
<h1 className="text-primary text-4xl">
Please wait we are verifying your email
</h1>
)}
{emailVerified == "true" && (
<h1 className="text-primary text-4xl">
Your email verified successfully
</h1>
)}
{emailVerified == "false" && (
<h1 className="text-primary text-4xl">
Invalid or Expired Token
{console.log(emailVerified)}
</h1>
)}
</div>
);
}
export default VerifyEmail;
Frontend => userSlice.js
import { createSlice } from "#reduxjs/toolkit";
export const userSlice = createSlice({
name: "user",
initialState: {
user: null
},
reducers: {
setUser: (state , action) => {
state.user = action.payload;
}
},
});
export const { setUser , reloadUserData } = userSlice.actions;
When I create/register user1 , the verification mail is sent to that email id and he(user1) is being verified successfully and I am able to change password for user1. After creating user1 , I am creating/registering user2 ,where the verification email is sent to the account .After clicking the link , it's becomes INVALID Overall , I am only able to create one user who's being verified
I've created an email authentication system, however there appears to be an issue with how I jwt.verify this token.
I believe there's an issue with my : process.env.PASS_SEC, which is just my Mongo.DB password secret. Is this correct?
I can confirm if I do a res.sent(req.params.token), my token comes through fine, for example in this.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYyZjc0MWU3ZjBkZjZkY2IyZjM0ZDc3ZSIsImlhdCI6MTY2MDM3MTQzMSwiZXhwIjoxNjYwNjMwNjMxfQ.vFtdRzEH2_52Hdhxs84bk7RPdIRDIoZ6Rcd-zZoBhus
As such, I believe it's the SECRET is being passed incorrectly.
My current functioning code is:
router.post("/register", async (req, res, EMAIL_SECRET) => {
const newUser = new User({
fullname: req.body.fullname,
email: req.body.email,
position: req.body.position,
username: req.body.fullname,
password: CryptoJS.AES.encrypt(
req.body.password,
process.env.PASS_SEC
).toString(),
});
const accessToken = jwt.sign(
{
id: newUser._id,
},
process.env.JWT_SEC,
{
expiresIn:"3d"
},
);
const url = `http://localhost:5000/api/auth/confirmation/${accessToken}`;
const mailOptions = {
from: 'nathandrewphone#gmail.com',
to: req.body.email,
subject: 'Confirm Email',
html: `Please click this email to confirm your email: ${url}`
};
transporter.sendMail(mailOptions, function(error, info){
if (error) {
console.log(error);
} else {
console.log('Email sent: ' + info.response);
}
});
try {
const savedUser = await newUser.save();
res.status(201).json(savedUser);
} catch (err) {
res.status(500).json(err);
}
});
Which sends a code fine, however it does not appear to be correct, how would you create an EMAIL_SECRET?
This is how I wish to validate the email.
//User Email Auth Login
//Not yet functioning
router.get('/confirmation/:token', async (req, res) => {
try {
//verify the token with the secret
const { _id: { _id } } = jwt.verify(req.params.token, process.env.PASS_SEC);
await models.User.update({ confirmed: true }, { where: { _id } });
} catch (e) {
res.send('This isnt working');
}
});
However, I cannot get to verify, whats wrong with secret
You signed your token with process.env.JWT_SEC, you should verify it using the same key:
const { _id } = jwt.verify(req.params.token, process.env.JWT_SEC);
Also, you should be able to update your User with findByIdAndUpdate:
await User.findByIdAndUpdate(_id, { confirmed: true });
The app crashes whenever I go to Google Sign in screen. I don't even have to click anything, it just crashes.
Error: The verifyIdToken method requires an ID Token at OAuth2Client.verifyIdTokenAsync (/app/node_modules/google-auth-
Google Login Implementation:
exports.googleController = (req, res) => {
try {
const { idToken } = req.body;
client
.verifyIdToken({ idToken, audience: process.env.GOOGLE_CLIENT })
.then((response) => {
console.log("GOOGLE LOGIN RESPONSE", response);
const { email_verified, name, email } = response.payload;
if (email_verified) {
User.findOne({ email }).exec((err, user) => {
if (user) {
const token = jwt.sign({ _id: user._id }, process.env.JWT_SEC, {
expiresIn: "7d",
});
const { _id, email, name, isAdmin } = user;
return res.json({
token,
user: { _id, email, name, isAdmin },
});
} else {
let password = email + process.env.JWT_SEC;
user = new User({ name, email, password });
user.save((err, data) => {
if (err) {
console.log("ERROR GOOGLE LOGIN ON USER SAVE", err);
return res.status(400).json({
error: "User signup failed with google",
});
}
const token = jwt.sign({ _id: data._id }, process.env.JWT_SEC, {
expiresIn: "7d",
});
const { _id, email, name, isAdmin } = data;
return res.json({
token,
user: { _id, email, name, isAdmin },
});
});
}
});
} else {
return res.status(400).json({
error: "Google login failed. Try again",
});
}
});
} catch (err) {
console.log(err);
}
};
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed last year.
Improve this question
I have implemented a login with hashed password authentication via Bcrypt in my Express Backend. Registration and login, as well as authenticated routes, work fine in the app, but not in the test environment. The following registration test succeeds, but the login test fails. When I log the password inside the user model's compare function, it is logged as plain text instead of the hash. I don't understand why though.
Any help is appreciated.
This is the users.test.js:
import { expect, server, BASE_URL } from './setup'
describe('User', () => {
it('registers a user', (done) => {
const data = { email: 'chester#benning.ton', name: 'chester', password: 'intheend' }
server
.post(`${BASE_URL}/users`)
.send(data)
.expect(201)
.end((err, res) => {
expect(res.status).to.equal(201)
expect(res.body).to.have.property('user')
expect(res.body.user).to.have.property('id')
expect(res.body.user).to.have.property('name', data.name)
done()
})
})
it('logs in a user', (done) => {
const data = { name: 'chester', password: 'intheend' }
server
.post(`${BASE_URL}/users/login`)
.send(data)
.expect(201)
.end((err, res) => {
expect(res.status).to.equal(201)
expect(res.body.user).to.have.property('id')
done()
})
})
})
The user model looks like this:
const { Model, Op } = require('sequelize')
const bcrypt = require('bcrypt')
const config = require('../../config/config')
function hashPassword(user) {
const saltRounds = config.saltRounds
if (!user.changed('password')) {
return false
}
return bcrypt.hash(user.password, saltRounds).then((hashedPassword) => {
user.setDataValue('password', hashedPassword)
})
}
module.exports = (sequelize, DataTypes) => {
class User extends Model {}
User.init({
email: {
allowNull: false,
type: DataTypes.STRING,
unique: true,
validate: {
isUnique(value, next) {
User.findOne({
where: {
email: {
[Op.iLike]: value.trim()
}
}
}).then((user) => {
if (user) {
return next('E-Mail is already registered')
}
return next()
}).catch(() => next('Could not be validated'))
}
}
},
name: {
allowNull: false,
type: DataTypes.STRING,
unique: true,
validate: {
isUnique(value, next) {
User.findOne({
where: {
name: {
[Op.iLike]: value.trim()
}
}
}).then((user) => {
if (user) {
return next('User name already exists')
}
return next()
}).catch(() => next('Could not be validated'))
}
}
},
password: {
allowNull: false,
type: DataTypes.STRING
}
}, {
hooks: {
beforeCreate: hashPassword,
beforeSave: hashPassword,
beforeUpdate: hashPassword
},
modelName: 'User',
sequelize,
tableName: 'Users'
})
User.prototype.comparePassword = async function comparePassword(password) {
return bcrypt.compare(password, this.password).then((res) => {
return res
}).catch((err) => {
return false
})
}
User.prototype.toJSON = function toJSON() {
const userObj = { ...this.get() }
delete userObj.password
return userObj
}
return User
}
And this is my UserController.js:
const { Op } = require('sequelize')
const jwt = require('jsonwebtoken')
const { User } = require('../models')
const config = require('../../config/config')
function jwtSignUser(user) {
const ONE_DAY = 60 * 60 * 24
return jwt.sign(user, config.authentication.jwtSecret, {
expiresIn: ONE_DAY
})
}
module.exports = {
findAll(req, res) {
const options = {
attributes: {
exclude: [ 'password' ]
},
order: [
[ 'name', 'ASC' ]
]
}
return User.findAll(options).then((users) => res.status(200).send(users)).catch(() => {
res.status(404).send({
message: 'Could not find users'
})
})
},
login(req, res) {
const { name, password } = req.body
User.findOne({
where: {
name: {
[Op.iLike]: name
}
}
}).then(async (user) => {
if (!user) {
return res.status(422).send({
message: 'Login information incorrect'
})
}
const passwordIsValid = await user.comparePassword(password)
if (!passwordIsValid) {
return res.status(422).send({
message: 'Login information incorrect'
})
}
const userJson = user.toJSON()
return res.status(201).send({
user: userJson,
token: jwtSignUser(userJson)
})
}).catch(() => {
res.status(500).send({
message: 'Login was not successful'
})
})
},
register(req, res) {
return User.create(req.body).then((user) => {
const userJson = user.toJSON()
return res.status(201).send({
user: userJson,
token: jwtSignUser(userJson)
})
}).catch((error) => {
res.status(422).send({
message: error.errors[0].message
})
})
}
}
In my application I want to get response from nodejs server. if I enter already registered mail id I want to get "User already exists" message on browser console.log in register.component.ts. How do it?
Many times tried but not able to findout. Please anyone help.
user.js://server
users.post('/register', (req, res) => {
const today = new Date()
const userData = {
first_name: req.body.first_name,
last_name: req.body.last_name,
email: req.body.email,
password: req.body.password,
created: today
}
User.findOne({
email: req.body.email
})
//TODO bcrypt
.then(user => {
if (!user) {
User.create(userData)
.then(user => {
const payload = {
_id: user._id,
first_name: user.first_name,
last_name: user.last_name,
email: user.email
}
let token = jwt.sign(payload, process.env.SECRET_KEY, {
expiresIn: 1440
})
res.json({ token: token })
})
.catch(err => {
res.send('error: ' + err)
})
} else {
res.json({ error: 'User already exists' })
}
})
.catch(err => {
res.send('error: ' + err)
})
})
authentication.service.ts:
public register(user: TokenPayload): Observable<any> {
const base = this.http.post(`/users/register`, user)
const request = base.pipe(
map((data: TokenResponse) => {
if (data.token) {
this.saveToken(data.token)
}
return data;
})
)
return request;
}
register.component.ts:
register() {
this.auth.register(this.credentials).subscribe(
() => {
this.router.navigateByUrl('/profile')
},
err => {
console.error(err); // how to get error message like "User already exits"
}
)
}