How can I create secure CRUD operation using MERN stack - node.js

I'm new to MERN stack and I did CRUD operation. I want to hash the password upon creation of new user, since I'm getting an error after creating a new user and trying to login "Invalid credentials" since the new user has been created with the password plain text and my registration compares the password with the hashed one
My create new user code:
exports.create = (req, res) => {
if(!req.body.name || !req.body.email || !req.body.password) {
return res.status(400).send({
message: "Name, Email and Password can not be empty"
});
}
const user = new User({
name: req.body.name.trim(),
email: req.body.email.trim(),
password: req.body.password.trim()
});
user.save()
.then(data => {
const user = usersSerializer(data)
res.send(user);
}).catch(err => {
res.status(500).send({
message: err.message || "Some error occurred while creating the User."
});
});
};

You must hash the user's password before storing it in the database. Do something like this.
router.post(
'/register',
[
check('email', 'Uncorrectly e-mail').isEmail(),
check('password', 'Uncorrectly password').isLength({ min: 6 })
],
async (req, res) => {
try {
const errors = validationResult(req)
if (!errors.isEmpty()) {
return res.status(400).json ({
errors: errors.array(),
message: 'Incorrect registration data'
})
}
console.log(req.body)
const { email, password, firstName, lastName } = req.body
const candidate = await User.findOne({ email })
if (candidate) {
return res.status(400).json({ message: 'User already exist' })
}
const hashedPassword = await bcrypt.hash(password,12)
const user = new User ({email, password: hashedPassword, firstName, lastName})
await user.save()
res.status(201).json({ message: 'New user created' })
} catch (error) {
res.status(500).json ({ message: 'ERROR' })
}
})

Related

Reactjs: post data to localhost always pending

I am working on ReactJs and NodeJS and I am creating a signup page. I post data to server but it is always pending.
Which part did I do wrong? It would be nice if someone can help.
Front end:
const handleSubmit = (event) => {
// prevent page refresh
event.preventDefault();
const newUserData = {
name: name,
email: email,
password: password,
};
axios
.post("/signup", newUserData)
.then((res) => {
console.log(res.data);
})
.catch((error) => {
console.log(error);
});
setEmail("");
setName("");
setPassword("")
console.log("form submitted ✅");
};
Backend:
router.post("/signup", (req, res) => {
const { name, email, password } = req.body;
if (!email || !password || !name) {
res.status(422).send({ error: "Please add all the fields" });
}
console.log(req.body);
User.findOne({ email: email })
.then((savedUser) => {
if (savedUser) {
res.status(422).send({ error: "Email already been used" });
}
bcrypt.hash(password, 12).then((hashedpassword) => {
const user = new User({
name,
email,
password: hashedpassword,
});
user
.save()
.then((user) => {
res.json({ message: "Sign Up Successfully" });
})
.catch((err) => {
console.log(err);
});
});
})
.catch((err) => {
console.log(err);
});
});
in package.json i set proxy as
"proxy": "http://localhost:5000",
I guess you are using MongoDB as well, in that case keep in your mind that the findOne is async, so you need to use await before. And for to save data you need to use the .create() method from MongoDB, e.g.
router.post("/signup", async (req, res) => {
const { name, email, password } = req.body;
if (!email || !password || !name) {
res.status(422).send({ error: "Please add all the fields" });
}
console.log(req.body);
await User.findOne({ email: email })
.then((savedUser) => {
if (savedUser) {
// you need to add return to stop the code
return res.status(422).send({ error: "Email already been used" });
}
// or you can add else because the code keep running
bcrypt.hash(password, 12).then((hashedpassword) => {
const user = await User.create({
name,
email,
password: hashedpassword,
});
user
.save()
.then((user) => {
res.json({ message: "Sign Up Successfully" });
})
.catch((err) => {
console.log(err);
});
});
})
.catch((err) => {
console.log(err);
});
});
I think it is better to use something like throw new Error('Email already been used') instead of return for your res.status(422).send({ error: "Email already been used" }); because if you have return the server doesn't give back an error, but a normal answer, but of course it is ok if you want that.
I want you to be sure that before you submit, the values name, email, password, are updated. Please try:
const handleSubmit = async (event) => {
// prevent page refresh
event.preventDefault();
console.log(`The value for the name: ${name}`);
console.log(`The value for the email: ${email}`);
console.log(`The value for the password: ${password}`);
try {
const response = await axios.post("http://localhost:5000/signup", {
name,
email,
password,
});
console.log(response.data);
setEmail("");
setName("");
setPassword("");
console.log("form submitted ✅");
} catch (error) {
console.log(error);
}
};

Solved: my code is unable to encrypt the password to save in mongoDB after email activation token is decrypted in node js, how can i solve it?

i am trying to implement jwt authentication by MERN and in nodejs i have used an email activation link to save the user email and password in mongodb. here is my working example for registering the user and activating the user. i am using sendgrid for the email.
//for email verification test
signup = async (req, res) => {
const { username, email, password, passwordCheck, displayName } = req.body;
//validate
if (!username || !email || !password || !passwordCheck)
return res.status(400).json({
msg: "not all fields have been entered.",
});
if (password.length < 8)
return res.status(400).json({
msg: "The password needs to be at least 8 characters long.",
});
if (password !== passwordCheck)
return res
.status(400)
.json({ msg: "Enter the same password twice for verification." });
const existingUser = await User.findOne({ email: email });
if (existingUser)
return res
.status(400)
.json({ msg: "An account with this email already exists." });
const existingUserName = await User.findOne({ username: username });
if (existingUserName)
return res
.status(400)
.json({ msg: "An account with this username already exists." });
if (!displayName) displayName = email;
const token = jwt.sign(
{ username, email, password, passwordCheck, displayName },
process.env.JWT_SECRET,{expiresIn:'1000m'}
);
const msg = {
to: email, //receiver's email
from: "no-reply#test.com", // Change to your verified sender
subject: `Email verification link ${displayName}`,
text: "testing from local",
html: `<h2>Hi ${displayName}</h2> <br/>sends a message for verification test: http://localhost:3000/authentication/activate/${token}</p> <br/><p>Have a nice day</p>`,
};
sgMail.setApiKey(process.env.SENDGRID_SECRET_API);
sgMail
.send(msg)
.then((result) => {
res.json({ message: "Email activation link has been sent" });
})
.catch((error) => {
console.error(error);
res.status(500).json("Error");
});
}
router.post("/register", signup);
userActivation = (req, res)=>{
const { token } = req.body;
if(token){
jwt.verify(token, process.env.JWT_SECRET,function(err,decodeToken){
if(err){
return res.status(400).json({error:'Incorrect or expired link.'})
}
const { username, email, password, passwordCheck, displayName }=decodeToken;
const newUser = new User({
username,
email,
password,
displayName,
});
newUser.save((err,success)=>{
if(err){
console.log("Error in signup with account activation",err)
return res.status(400).json({error:"Error activating account"})
}
res.json({
message:"signup Success!!"
})
});
} );
} else{
return res.json({error:"Something went wrong"})
}
}
router.post("/email-activate",userActivation)
while using the postman to save the user with activation key, in mongodb the password is saving in plain text. i don want it to be saved in plain text because of security issue. i want it to be saved in encrypted and tried to use the below code:
userActivation = async (req, res) => {
const { token } = req.body;
if (token) {
jwt.verify(token, process.env.JWT_SECRET, function (err, decodeToken) {
if (err) {
return res.status(400).json({ error: "Incorrect or expired link." });
}
const { username, email, password, displayName } = decodeToken;
console.log(password)
User.findOne({ email }).exec((err, user) => {
if (user) {
return res.status(400).json({ error: "Username with this email exists." })
}
const salt = bcrypt.genSalt();
bcrypt.hash(password, salt, (err, passwordHash)=>{
const newUser = new User({
username,
email,
password: passwordHash,
displayName,
});
console.log(password)
console.log(passwordHash)
newUser.save((err, success) => {
if (err) {
console.log("Error in signup with account activation", err);
return res.status(400).json({ error: "Error activating account" });
}
res.json({
message: "signup Success!!",
});
})
})
})
})
}
}
when i start my server and try to sign in with activation key through postman it sends me an activation link. when i try to send the post request through the postman in activation link,postman shows 404 status with "error activating account" and node index shows the following error:
The server has started on port: 5000
MongoDB connected
**the real password is showing undecoded**
**the real password is showing undecoded**
undefined
Error in signup with account activation Error: user validation failed: password: Path `password` is required.
at ValidationError.inspect (C:\Myfiles\Reactjs\Projects\test-projects\node_modules\mongoose\lib\error\validation.js:47:26)
at formatValue (internal/util/inspect.js:731:31)
at inspect (internal/util/inspect.js:295:10)
at formatWithOptionsInternal (internal/util/inspect.js:1958:40)
at formatWithOptions (internal/util/inspect.js:1842:10)
at Object.value (internal/console/constructor.js:306:14)
at Object.log (internal/console/constructor.js:341:61)
at C:\Myfiles\Reactjs\Projects\test-projects\routes\userRouter.js:243:37
at C:\Myfiles\Reactjs\Projects\test-projects\node_modules\mongoose\lib\model.js:4863:16
at C:\Myfiles\Reactjs\Projects\test-projects\node_modules\mongoose\lib\helpers\promiseOrCallback.js:16:11
at C:\Myfiles\Reactjs\Projects\test-projects\node_modules\mongoose\lib\model.js:4886:21
at C:\Myfiles\Reactjs\Projects\test-projects\node_modules\mongoose\lib\model.js:500:16
at C:\Myfiles\Reactjs\Projects\test-projects\node_modules\kareem\index.js:247:48
at next (C:\Myfiles\Reactjs\Projects\test-projects\node_modules\kareem\index.js:168:27)
at next (C:\Myfiles\Reactjs\Projects\test-projects\node_modules\kareem\index.js:170:9)
at Kareem.execPost (C:\Myfiles\Reactjs\Projects\test-projects\node_modules\kareem\index.js:218:3) {
errors: {
password: ValidatorError: Path `password` is required.
at validate (C:\Myfiles\Reactjs\Projects\test-projects\node_modules\mongoose\lib\schematype.js:1256:13)
at C:\Myfiles\Reactjs\Projects\test-projects\node_modules\mongoose\lib\schematype.js:1239:7
at Array.forEach (<anonymous>)
at SchemaString.SchemaType.doValidate (C:\Myfiles\Reactjs\Projects\test-projects\node_modules\mongoose\lib\schematype.js:1184:14)
at C:\Myfiles\Reactjs\Projects\test-projects\node_modules\mongoose\lib\document.js:2502:18
at processTicksAndRejections (internal/process/task_queues.js:75:11) {
properties: [Object],
kind: 'required',
path: 'password',
value: undefined,
reason: undefined,
[Symbol(mongoose:validatorError)]: true
}
},
_message: 'user validation failed'
}
According to above try the code is unable to encrypt the password and hence it can't be saved in mongodb. what did i do wrong to encode the password?
so how can i solve it?
thanx in advance
const passwordHash = bcrypt.hash(password, salt);
Here bcrypt.hash returning promise either your can use async/await or use .then().
userActivation = async (req, res) => {
const { token } = req.body;
if (token) {
jwt.verify(token, process.env.JWT_SECRET, function (err, decodeToken) {
if (err) {
return res.status(400).json({ error: "Incorrect or expired link." });
}
const { username, email, password, displayName } = decodeToken;
console.log(password);
User.findOne({ email }).exec((err, user) => {
if (user) {
return res.status(400).json({ error: "Username with this email exists." })
}
//Use genSaltSync when you donot want to use await or your can use await bcrypt.genSalt()
const salt = bcrypt.genSaltSync(10);
bcrypt.hash(password, salt, (err, passwordHash)=>{
const newUser = new User({
username,
email,
password: passwordHash,
displayName,
});
newUser.save((err, success) => {
if (err) {
console.log("Error in signup with account activation", err);
return res.status(400).json({ error: "Error activating account" });
}
res.json({
message: "signup Success!!",
});
})
})
})
})
}
}
Try this code once just put await before bcrypt and made function async.

Invalid email or password

don't know whats going on wrong,when i am trying to post request through postman i am getting an error like "Invalid email or password". in sign in. please help
signup
below is my signup request where i am doing my signup validation.
const User = require('../model/user');
const bcrypt = require('bcryptjs');
exports.signup = (req, res) => {
const { name, email, password } = req.body;
if (!name || !email || !password) {
res.status(422).json({
error: "please add all field"
})
}
User.findOne({ email: email })
.then((SavedUser) => {
if (SavedUser) {
return res.status(400).json({
error: "User already exists that email"
})
}
const user = new User({
email,
password,
name
})
user.save()
.then(user => {
res.json({
message: "saved Successfully"
})
.catch(err => {
console.log(err);
})
})
.catch(err => {
console.log(err);
})
})
}
Signin
below is my signin form where i doing my signin operation
exports.signin = (req, res) => {
const { email, password } = req.body;
if (!email || !password) {
res.status(422).json({
error: "please enter email and password"
})
}
User.findOne({ email: email })
.then(SavedUser => {
if (!SavedUser) {
return res.status(400).json({
error: "invalid email or password"
})
}
bcrypt.compare(password, SavedUser.password)
.then(doMatch => {
if (doMatch) {
res.json({
message: "Successfully Signed in"
})
}
else {
return res.status(422).json({
error: "Invalid email or password"
})
}
})
.catch(err => {
console.log(err);
})
})
}
It seems you're not hasing the password, when creating a new mongoose user-object. Obvioulsy, bcrypt.compare(password, SavedUser.password) will then fail. Try to do it like this (note I'm using async/await here instead of promises directly):
password = await bcrypt.hash(password, 10);
const user = new User({
email,
password,
name
});
you didn't bcrypt your password at the time of saving.
You can make a pre save function in your schema like this.
// Hash the plain text password before saving
User.pre("save", async function (next) {
const user = this;
try {
if (user.isModified("password")) {
user.password = await bcrypt.hash(user.password, 8);
}
next();
} catch (error) {
next(error);
}
});

Why does transaction fails with already rolled back when user inputs in wrong email

I'm authorizing emails in the database but when I input wrong email it throws Transaction cannot be rolled back because it has been finished with state: commit
export const signin = async (req, res) => {
const { email, password } = req.body;
const transaction = await db.sequelize.transaction();
try {
const user = await db.User.findOne({ where: { email } }, { transaction });
await transaction.commit();
const passBool = Auth.comparePassword(password, user.password);
if (user && passBool) {
return res.json({
success: 1,
user,
message: 'signed in'
});
}
res.json({ success: 0, message: 'wrong username or password' });
} catch (ex) {
await transaction.rollback();
res.json({ success: 0, message: 'wrong username or password' });
}
};
I'm not sure exactly why rolling back doesn't work in your example, but you can try:
A transaction should be passed in the options object of the query:
const user = await db.User.findOne({ where: { email }, transaction });
You can try using managed transaction, to avoid manual handling of commit/rollback:
export const signin = async (req, res) => {
const { email, password } = req.body;
db.sequelize.transaction(async transaction => {
const user = await db.User.findOne({ where: { email }, transaction });
const passBool = Auth.comparePassword(password, user.password);
if (user && passBool) {
return res.json({
success: 1,
user,
message: 'signed in'
});
}
res.json({ success: 0, message: 'wrong username or password' });
}).catch(err => {
res.json({ success: 0, message: 'wrong username or password' });
});
};
solved this, it would fail if i had inserted in wrong request body params

How to get the return of a created object in Mongoose with express api

In my express api, using mongoose, i create a new user on api using User.create. How i can get the return of the created object to include on the response?
My method to create a object is:
async store (req, res) {
const { name, lastName, email, password } = req.body
if (await User.findOne({ email })) {
return res.status(400).json({ error: 'User already exists' })
}
try {
await User.create({
name,
lastName,
email,
password,
role: 'USER_ROLE'
})
res.status(201).json({
ok: true,
message: 'User created'
})
} catch (e) {
res.status(500).json({
ok: false,
message: 'Failed to create User'
})
}
}
I want to return the user created on the res, like:
res.status(201).json({
ok: true,
message: 'User created',
user: user
})
async store(req, res) {
const { name, lastName, email, password } = req.body
if (await User.findOne({ email })) {
return res.status(400).json({ error: 'User already exists' })
}
try {
let user = await User.create({
name,
lastName,
email,
password,
role: 'USER_ROLE'
})
user = user.toJson();
delete user.password;
delete user.role;
res.status(201).json({
ok: true,
user: user,
message: 'User created'
})
} catch (e) {
res.status(500).json({
ok: false,
message: 'Failed to create User'
})
}
}
You can covert mongoose object to JSON using
user.toJson()
Then you can delete the fields that you don't want to send into the response.
delete user.password;

Resources