Related
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);
}
};
// auth: {
// user: "shanshangeeth#gmail.com",
// pass: "erwsvgtamrplzssl",
// },
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;
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
In the "verifyemail" route handler is you are trying to access the body of the req which is null, remember that when a user clicks on that URL in the email, a get request is send. The token will then exist in the req.params object, Not req.body.
Try the changes below.
router.get("/verifyemail/:token", async (req, res) => {
try {
const tokenData = await Token.findOne({ token: req.params.token });
if (tokenData) {
await User.findOneAndUpdate({ _id: tokenData.userid, isVerified: true });
await Token.findOneAndDelete({ token: req.params.token });
res.send({ success: true, message: "Email Verified Successlly" });
}
I am testing on postman with passport-jwt, I got my user's token, and try to test protected routes with passport-jwt, following my code, I am supposed to get "hello", but somehow still not allow me to authorize, and it keep gives me "401 unauthorized", can anyone help if my code is wrong? thanks!
on my postman, i have attached authorization, and Bearer token under header.
My middleware:
const jwt = require("jsonwebtoken");
const User = require("../models/user");
const { signupUser, loginUser } = require("../controller/auth");
const passport = require("passport");
const JwtStrategy = require("passport-jwt").Strategy,
ExtractJwt = require("passport-jwt").ExtractJwt;
const opts = {
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: "process.env.JWT_SECRET",
};
module.exports = (passport) => {
passport.use(
new JwtStrategy(opts, async (payload, done) => {
await User.findById(payload.id)
.then((user) => {
if (user) {
return done(null, user);
}
return done(null, false);
})
.catch((err) => {
console.log(err);
return done(null, false);
});
})
);
};
the route file:
const express = require("express");
const router = express.Router();
const { userAuth, signupUser, loginUser } = require("../controller/auth");
router.get("/category/getcategory", userAuth, async (req, res) => {
return res.json("hello");
});
module.exports = router;
My controller file:
const User = require("../models/user");
const jwt = require("jsonwebtoken");
const bcrypt = require("bcrypt");
const passport = require("passport");
exports.loginUser = async (req, role, res) => {
const { email, password } = req;
try {
const user = await User.findOne({ email });
if (!user)
return res.status(400).json({ message: "User does not exists." });
const isPasswordCorrect = await bcrypt.compare(password, user.password);
if (!isPasswordCorrect)
return res.status(400).json({ message: "Invalid credentials." });
if (!user.role == role) {
return res.status(403).json({ message: "please check the right portal" });
}
const payload = {
email: user.email,
id: user._id,
role: user.role,
};
const token = jwt.sign(payload, process.env.JWT_SECRET, {
expiresIn: "1h",
});
let result = {
mail: user.email,
id: user._id,
role: user.role,
token: `Bearer ${token}`,
expiresIn: 168,
};
res.status(200).send({ ...result, Message: "Now you are logged in!" });
} catch (error) {
console.log(error);
}
};
exports.signupUser = async (req, role, res) => {
const { firstName, lastName, email, password, confirmPassword } = req;
try {
const user = await User.findOne({ email });
if (user) return res.status(400).json({ message: "User already exists." });
if (!password == confirmPassword)
return res.status(400).json({ message: "Password don't match" });
const hashedPassword = await bcrypt.hash(password, 12);
const result = await User.create({
email,
password: hashedPassword,
firstName,
lastName,
role,
});
const token = jwt.sign(
{ email: result.email, id: result._id },
process.env.JWT_SECRET,
{ expiresIn: "1h" }
);
res.status(200).json({ result, token });
} catch (error) {
console.log(error);
}
};
exports.userAuth = passport.authenticate("jwt", { session: false });
So I create my route user and I use this tutorial https://www.bezkoder.com/node-js-jwt-authentication-mysql/ to add the token and Authentication Token + validation name. Everything work great. But then when I beginning to create my route Comment (acces for every user), I had normaly the auth but even with the token Postman send me an "No token provided!". I have to add this token and autorize the acces but I don't know how. If somebody know how to do it, it would be great :D thx ! This is how I add my token on Postman
enter image description here
Here is my code:
My route comment:
const express = require("express");
const router = express.Router();
const commentCtrl = require("../controllers/comment");
const { authJwt } = require("../middleware");
router.post("/upload", [authJwt.verifyToken], commentCtrl.createComment);
module.exports = router;
The middleware token :
const jwt = require("jsonwebtoken");
const config = require("../config/auth.config.js");
verifyToken = (req, res, next) => {
let token = req.headers["x-access-token"];
if (!token) {
return res.status(403).send({
message: "No token provided!"
});
}
jwt.verify(token, config.secret, (err, decoded) => {
if (err) {
return res.status(401).send({
message: "Unauthorized!"
});
}
req.id = decoded.id;
next();
});
};
const authJwt = {
verifyToken: verifyToken,
};
module.exports = authJwt;
const authJwt = require("./authJwt");
const verifySignUp = require("./verifySignUp");
module.exports = {
authJwt,
verifySignUp
};
My verification SignUp:
const { User } = require("../models");
checkDuplicateEmail = async (req, res, next) => {
const user = await User.findOne({
where: {
email: req.body.email
}
}).then(user => {
if (user) {
res.status(400).send({
message: "Failed! Email is already in use!"
});
return;
}
next();
});
};
const verifySignUp = {
checkDuplicateEmail: checkDuplicateEmail
};
module.exports = verifySignUp;
And here is my user.controllers when is send the token:
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
const config = require("../config/auth.config");
const { User } = require("../models");
module.exports.signup = async (req, res) => {
if (!req.body.email || !req.body.password) {
res.status(400).send({
status: false,
message: 'une erreur dans create est survenu'
});
} else {
let { nom, prenom, email, password, status} = req.body;
const salt = await bcrypt.genSalt(10);
password = await bcrypt.hash(password, salt)
const user = await User.create({
nom, prenom, email, password, status
})
.then((user) => res.status(201).send(user)).catch((error) => {
console.log(error);
res.status(400).send(error);
});
}
};
module.exports.login = async (req, res) => {
const user = await User.findOne({
where: {
email: req.body.email
}
})
.then(user => {
if (!user) {
return res.status(404).send({ message: "User Not found." });
}
const passwordIsValid = bcrypt.compareSync(req.body.password, user.password);
if (!passwordIsValid) {
return res.status(401).send({
accessToken: null,
message: "Invalid Password!"
});
}
var token = jwt.sign({ id: user.id }, config.secret, {
expiresIn: 86400 // 24 hours
});
res.status(200).send({
id: user.id,
nom: user.nom,
email: user.email,
password: user.password,
accessToken: token
});
})
.catch(err => {
res.status(500).send({ message: err.message });
});
};
While it would have helped if you had provided a screenshot of where you are putting your token on postman, make sure you are using the appropriate header. According to your code, you are searching the 'x-access-token' header, so in postman, you should attach your jwt token to the same header property. It should be something like this:
postman x-access-token demo
I want to do my login API with Node.js and MongoDB and when I compare my pass from input to the one that's in the db I always get false I read other post on StackOverFlow but didn't helped me.
I think I also found the problem: When I use hash on my password input to check it manually with the one from db at every request is onatherone.
So maybe that's the problem but I don't know how to solve. I read a lot about this but still cant solve here is my code:
const match = await bcrypt.compare(password, user.password, (res) => {console.log(res)}) //false
my login api
router.post('/login', body('email').isEmail(), body('password').exists(), async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() })
}
const { email, password } = req.body
const user = await User.findOne({ email })
if (!user) {
return res.status(401).json({ "err": "invalid credentials" })
}
// const match = await bcrypt.compare(password, user.password).then(function (res) { console.log(res) })
const match = await bcrypt.compare(password, user.password);
console.log(match)
})
and here is the register api
router.post("/register", body("email").isEmail(), body("password").isLength({ min: 5 }), body("username").isLength({ min: 1 }), async (req, res) => {
const { email, username, password } = req.body
const errors = validationResult(req);
const saltRounds = 10;
try {
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() })
}
//check if the user exist
const duplicated = await User.findOne({ email })
if (duplicated) { return res.status(401).json({ "err": "Email is taken" }) }
const user = new User({ username, email, password })
//crypt pass
user.password = bcrypt.hashSync(process.env.secret, saltRounds);
//generate token with jwt
const payload = {
id: user.id
}
jwt.sign({
payload,
}, process.env.jwtSecret, { expiresIn: '999h' }, (err, token) => { console.log(token) });
//save the user
await user.save()
res.status(200).send("User Stored")
} catch (error) {
res.status(500).send(error.body)
}
})
Your problem is that you using bcrypt.hash in the wrong way.
It seems like you provide this method kind of key, which you shall not provide.
This method accepts the value to hash, and saltRounds.
So you basically need to change your Registration API code to:
router.post("/register", body("email").isEmail(), body("password").isLength({ min: 5 }), body("username").isLength({ min: 1 }), async (req, res) => {
const { email, username, password } = req.body
const errors = validationResult(req);
const saltRounds = 10;
try {
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() })
}
//check if the user exist
const duplicated = await User.findOne({ email })
if (duplicated) { return res.status(401).json({ "err": "Email is taken" }) }
const user = new User({ username, email, password })
//crypt pass
user.password = bcrypt.hashSync(password, saltRounds);
//generate token with jwt
const payload = {
id: user.id
}
jwt.sign({
payload,
}, process.env.jwtSecret, { expiresIn: '999h' }, (err, token) => { console.log(token) });
//save the user
await user.save()
res.status(200).send("User Stored")
} catch (error) {
res.status(500).send(error.body)
}
}
And more specific, you store the password with:
user.password = bcrypt.hashSync(password, saltRounds);
try use Promise for this
const match = await new Promise((resolve, reject) => {
bcrypt.compare(password, user.password, function(error, res){
if (error) { reject(error); }
resolve(res);
})
})
I have an API in ExpressJS. Within that API I have a login endpoint, when posting to that endpoint however I keep getting the exception that headers cannot be set after they have been sent.
I understand this is normally a callback that is being called twice or not properly returning from something that has set headers, causing the app to attempt to set them again, however in my /login endpoint I am not doing this.
I cannot understand why this happening, I would love some input as to why as I am close to pulling my hair out reading the same replies and answers. I hope it is something obvious I am missing.
import User from '../../models/user';
import { Router } from 'express';
import jwt from 'jsonwebtoken';
export default () => {
const route = Router();
route.post('/create', async (req, res, next) => {
if (!req.body.email || !req.body.password) {
return res
.status(400)
.json({ message: 'username or password is missing' });
}
const { email, password } = req.body;
const count = await User.count({ email });
if (count > 0) {
return res.status(409).json({ message: 'email must be unique' });
}
const newUser = await new User({ email, password });
const doc = await newUser.save();
return res.status(201).json({ type: 'account', attributes: doc });
});
route.post('/login', async (req, res, next) => {
if (req.body.email && req.body.password) {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (user) {
user.comparePassword(password, isMatch => {
if (isMatch) {
const token = jwt.sign(
{ sub: user.id, roles: [], email: user.email },
process.env.SECRET_KEY,
{ expiresIn: '12h' },
);
return res
.status(200)
.json({ type: 'account', attributes: { token } });
}
});
}
}
res.sendStatus(401);
});
return route;
};
import User from '../../models/user';
import { Router } from 'express';
import jwt from 'jsonwebtoken';
export default () => {
const route = Router();
route.post('/create', async (req, res, next) => {
if (!req.body.email || !req.body.password) {
return res
.status(400)
.json({ message: 'username or password is missing' });
}
const { email, password } = req.body;
const count = await User.count({ email });
if (count > 0) {
return res.status(409).json({ message: 'email must be unique' });
}
const newUser = await new User({ email, password });
const doc = await newUser.save();
return res.status(201).json({ type: 'account', attributes: doc });
});
route.post('/login', async (req, res, next) => {
if (req.body.email && req.body.password) {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (user) {
return user.comparePassword(password, isMatch => {
if (isMatch) {
const token = jwt.sign(
{ sub: user.id, roles: [], email: user.email },
process.env.SECRET_KEY,
{ expiresIn: '12h' },
);
return res
.status(200)
.json({ type: 'account', attributes: { token } });
} else {
return res.status(400)
.json({ message: 'username or password is invalid' });
}
});
}
}
res.sendStatus(401);
});
return route;
};
Have a look at the updated code return was missing at
return user.comparePassword(password, isMatch => {
Hope it'll fix your issue.
The problem in here. Your callback in comparePassword return only inside that callback. So the code still run to res.sendStatus(401) and after the callback is done it will run res.status(200).json...
user.comparePassword(password, isMatch => {
if (isMatch) {
const token = jwt.sign(
{ sub: user.id, roles: [], email: user.email },
process.env.SECRET_KEY,
{ expiresIn: '12h' },
);
return res
.status(200)
.json({ type: 'account', attributes: { token } });
}
});
Try to promisify comparePassword method in the user model:
userSchema.methods.comparePassword = function (password) {
return new Promise( function(resolve, reject) {
resolve(password === this.password);
});
}
Now you can use await syntax to get the promise result:
route.post('/login', async (req, res, next) => {
if (req.body.email && req.body.password) {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (user) {
const isMatch = await user.comparePassword(password);
if (isMatch) {
const token = jwt.sign(
{ sub: user.id, roles: [], email: user.email },
process.env.SECRET_KEY,
{ expiresIn: '12h' },
);
return res
.status(200)
.json({ type: 'account', attributes: { token } });
}
}
}
res.sendStatus(401);
});