I am experimenting with node authentication, I have managed to store a username and a hashed password into my database, but I want to return the json back without the hashed password.
I am deleting the password key before sending the JSON back but the password still shows in the returned result.
router.post("/signup", async (req, res, next) => {
const user = await User.exists({ username: req.body.username });
if (user) {
const error = new Error("Username already exists");
next(error);
} else {
const newUser = new User({
username: req.body.username,
password: req.body.password,
});
try {
const result = await newUser.save();
delete result.password;
res.json(result);
} catch (err) {
res.json(err.errors);
}
}
});
the User model has a pre hook to hash the password before save:
userSchema.pre("save", async function save(next) {
const user = this;
if (!user.isModified("password")) return next();
try {
user.password = await bcrypt.hash(user.password, 12);
return next();
} catch (err) {
return next(err);
}
});
Here is the solution thanks to Mahan for pointing it out.
result returns a Mongoose object so needs turning into a normal Javascript object first.
try {
let result = await newUser.save();
result = result.toObject();
delete result.password;
res.json(result);
} catch (err) {
res.json(err.errors);
}
Related
I am trying to make a login route in node.js but when ever I enter wrong credential my application crashes
This is my Login route, Please tell me what I am doing wrong
router.post("/login", async (req, res) => {
try {
const user = await User.findOne({ email: req.body.email });
!user && res.status(400).json("User Not exist!");
const validated = await bcrypt.compare(req.body.password, user.password);
!validated && res.status(400).json("Invalid Password!");
const { password, ...others } = user._doc;
res.status(200).json(others);
} catch (err) {
res.status(500).json(err);
}
});
Can you just add a return before the responses and make your code cleaner the problem is that when you write res.status(400)... the code will continue to the next steps and the app will crash so your code should be like that
router.post("/login", async (req, res) => {
try {
const user = await User.findOne({ email: req.body.email });
if (!user) {
return res.status(400).json("User Not exist!");
}
const validated = await bcrypt.compare(req.body.password, user.password);
if (!validated) {
return res.status(400).json("Invalid Password!");
}
const { password, ...others } = user._doc;
return res.status(200).json(others);
} catch (err) {
return res.status(500).json(err);
}
});
const router = require("express").Router();
const user = require("../models/user");
const cryptoJs = require("crypto-js");
const dotenv = require("dotenv").config();
router.post("/register", async (req, res) => {
const newUser = new user({
username: req.body.username,
password: cryptoJs.AES.encrypt(req.body.password, process.env.pass),
});
try {
const savedUser = await newUser.save();
res.status(201).json(savedUser);
} catch (error) {
res.status(500).json(error);
}
});
router.post("/login", async (req, res) => {
try {
const oneUser = await user.findOne({ username: req.body.username });
if (!oneUser) {
res.status(401).json("Wrong credentials");
}
const hp = cryptoJs.AES.decrypt(oneUser.password, process.env.pass);
const password = hp.toString(cryptoJs.enc.Utf8);
if (password !== req.body.password) {
res.status(401).json("Wrong credentials");
}
res.status(200).json(oneUser);
} catch (error) {
res.sendStatus(500).json(error);
}
});
module.exports = router;
//so, there is the code! everything works fine up to the /login section. when I input the right username and password, it gets me the matching user from the database, but when I input the wrong username and the right password immediately after, it says "wrong credentials which is also fine. But when I input the wrong password after all the previous inputs, it brings this error " Cannot set headers after they are sent to the cliententer code here"
The set header error when will display that you send/return two "res" so use you have to use if-else not if
So the problem is that you send a response to the client, while you already sent a response to the client. When the password is different, you send "Wrong Credentials", but the script will also try to send the oneUser Mongo Object.
To get rid of that, either use an if .. else .. like #Evan proposed, either return the response so you're sure that the script stop there.
The "if/else" solution
if (password !== req.body.password) {
res.status(401).json("Wrong credentials");
}
else {
res.status(200).json(oneUser); // will be sent if the condition before is not completed
}
The "return" solution
if (password !== req.body.password) {
return res.status(401).json("Wrong credentials"); // if the password is different, this will stop the script here
}
res.status(200).json(oneUser);
its better you improve youre block condition like
if (condition){
// do something
}
else {
//do something else
}
OR you can return youre response . it means that when you want to send response return something and exit from the function .
this solution in your code is
router.post("/register", async (req, res) => {
const newUser = new user({
username: req.body.username,
password: cryptoJs.AES.encrypt(req.body.password, process.env.pass),
});
try {
const savedUser = await newUser.save();
return res.status(201).json(savedUser);
} catch (error) {
return res.status(500).json(error);
}
});
router.post("/login", async (req, res) => {
try {
const oneUser = await user.findOne({ username: req.body.username });
if (!oneUser) {
return res.status(401).json("Wrong credentials");
}
const hp = cryptoJs.AES.decrypt(oneUser.password, process.env.pass);
const password = hp.toString(cryptoJs.enc.Utf8);
if (password !== req.body.password) {
return res.status(401).json("Wrong credentials");
}
return res.status(200).json(oneUser);
} catch (error) {
return res.sendStatus(500).json(error);
}
});
I am trying to hash a password after updating it but I dont understand why it is just working after the await line. In the res.json I get the hashed password, but just there.
I am new to this so I appreciate any help or advice.
router.put('/:id', async (req, res) => {
let { mail, password } = req.body;
bcrypt.genSalt(saltRounds, function (err, salt) {
if (err) return next(err);
bcrypt.hash(password, salt, function (err, hash) {
if (err) return next(err);
password = hash;
});
});
const newUser = { mail, password };
await User.findByIdAndUpdate(req.params.id, newUser);
res.json({ mensaje: `Updated Password ${password}` });
});
As per my comment, you should look into async/await and callbacks more to understand the call order. As it's not running in the sequential fashion you think it is. But you can try the following.
router.put('/:id', async (req, res) => {
let { mail, password } = req.body;
try{
const salt = await bcrypt.genSalt(saltRounds);
const hashedPassword = await bcrypt.hash(password, salt);
const newUser = { mail, password };
await User.findByIdAndUpdate(req.params.id, newUser);
res.json({ mensaje: `Updated Password ${password}` });
} catch(error) {
res.json(error);
}
});
I'm facing a weird problem
I'm using mongoose 5.12.8, I try to delete a user's image with this route
router.delete('/users/me/avatar', auth, async(req, res) => {
try {
req.user.avatarObj = undefined
await req.user.save()
res.send({ message: "Deleted user's avatar" })
} catch (error) {
res.status(500).send(error.message)
}
});
but when I use get route, i got req.user.avatarObj is still an empty object {}
//get user avatar
router.get('/users/:id/avatar', async(req, res) => {
try {
const user = await User.findById(req.params.id);
console.log(user.avatarObj) // print {} ?????????
console.log(user) //user has no avatarObj field
if (user && user.avatarObj) {
res.set('Content-Type', user.avatarObj.contentType);
res.send(user.avatarObj.data);
} else {
throw new Error('Not found user or image');
}
} catch (err) {
res.status(404).send({ error: err.message });
}
});
this is my userSchema
avatarObj: {
data: Buffer,
contentType: String
}
and some middleware
//hash plain text password before saving
userSchema.pre('save', async function(next) {
const user = this
if (user.isModified('password')) {
user.password = await bcrypt.hash(user.password, 10)
}
next()
});
//remove all tasks before remove user
userSchema.pre('remove', async function(next) {
await Task.deleteMany({ owner: this._id })
next()
});
My question is: Why user.avatarObj is still an empty object?
Thank you guys!
I have an API / express router:
router.post("/signup", async function (req, res) {
try {
var user = await controllers.user.register(req.body.username, req.body.password);
req.session.user = user;
res.json(user);
} catch (e) {
res.status(500).json("DB Error");
}
});
Currently, on error, it returns 500 DB error. This is my controller:
function register(username, password) {
return new Promise((resolve, reject) => {
User.findOne({ username: username }).lean().exec((e, doc) => {
if (e) reject(e);
if (doc) {
reject("Username already exists.");
} else {
var user = new User({ username, password: hash(password) });
user.save((e) => {
if (e) reject(e);
else {
delete user.password;
resolve(user);
}
});
}
});
});
}
What's the right way to return a 400 if username already exists, and a 500 if it was a database error?
Mongoose already uses promises, the use of new Promise is promise construction antipattern.
Express doesn't have the concept of controllers, there are only route handlers and middlewares. Since register should be very aware of the way it will be used in a response, there may be no need for another level of abstraction above route handler. There will be no problem when a function has access to handler parameters and can form a response in-place.
It can be:
router.post("/signup", async function (req, res) {
try {
const { body, password } = req.body;
const user = await User.findOne({ username: username }).lean();
if (user) {
res.status(400).json("Username already exists");
} else {
...
res.json(user);
}
} catch (e) {
res.status(500).json("DB Error");
}
});
In case route handler needs to be reused in multiple places with some variations, it could be refactored to higher-order function or some other helper that is aware of original req and res parameters.
You can change the way you are rejecting the Promise. I'd suggest something like:
function register(username, password) {
return new Promise((resolve, reject) => {
User.findOne({ username: username }).lean().exec((e, doc) => {
if (e) reject(500);
if (doc) {
reject(400);
} else {
var user = new User({ username, password: hash(password) });
user.save((e) => {
if (e) reject(500);
else {
delete user.password;
resolve(user);
}
});
}
});
});
}
And in the route:
router.post("/signup", async function (req, res) {
try {
var user = await controllers.user.register(req.body.username, req.body.password);
req.session.user = user;
res.json(user);
} catch (e) {
res.status(e).json(e == 400 ? "Username already exists." : "DB Error");
}
});