automatic logout after change any user info passport-local-mongoose - node.js

i tried req.login which is a passport method which i think its used when we sign up only but it didnt work and i see this solution Passport-Local-Mongoose – When I Update A Record's Username, I'm Logged Out, Why? but it didn`t work and
async (req, res, next) => {
const {id} = req.params;
if (req.file) {
const {path, filename} = req.file;
const foundUser = await User.findByIdAndUpdate(id, {
profileImage: {profileUrl: path, filename},
});
}
const foundUser = await User.findByIdAndUpdate(id, req.body.user);
req.logIn(foundUser, function (err) {
if (err) {
return next(err);
}
return res.redirect("/user/" + foundUser.username);
});
}
);

i found the solution the problem happens when you change username and passport is saving your username already on the session {user: ''} so when u change with new username we don`t update passport user with new username
so try this
try {
const foundUser = await User.findByIdAndUpdate(id, req.body.user);
req.session.passport.user = req.body.user.username;
return res.redirect("/users/" + id);
} catch (e) {
const duplicated = Object.keys(e.keyPattern);
return next(
new AppError(
`this ${duplicated} already in use, you can enter another ${duplicated} `,
406
)
);
}

Related

SyntaxError: await is only valid in async functions and the top level bodies of modules - NodeJS

I am trying to create user login and sign up in NodeJS with mongoDB, but in login module i am getting this error -
existingUser = await User.findOne({email: email});
^^^^^
SyntaxError: await is only valid in async functions and the top level
bodies of modules.
Here is my code of "user-controller.js" file code.
const User = require('../model/User');
const bcrypt = require('bcryptjs');
// next is used to move to the next middleware task
const signup = async (req, res, next) => {
const { name, email, password } = req.body;
let existingUser;
try {
existingUser = await User.findOne({ email: email });
} catch (err) {
console.log(err);
}
if (existingUser) {
return res.status(400).jason({ message: 'User already exists! Login Instead' })
}
const hashedPassword = bcrypt.hashSync(password);
const user = new User({
name,
email,
password: hashedPassword,
});
try {
await user.save();
} catch (err) {
console.log(err);
}
return res.status(201).json({ message: user })
};
const login = (req, res, next) => {
const { email, password } = req.body;
let existingUser;
try {
existingUser = await User.findOne({ email: email });
} catch (err) {
return new Error(err);
}
if (!existingUser) {
return res.status(400).json({ message: "User not found. Signup Please" })
}
const isPasswordCorrect = bcrypt.compareSync(password, existingUser.password);
if (!isPasswordCorrect) {
return res.status(400).json({ message: "Invalid Email / Password" })
}
return res.status(200).json({ message: "Successfully logged in" })
}
exports.signup = signup;
exports.login = login;
How to resolve it?
We can only use await inside an async function, in your case
const login = async (req, res, next) => {
// We can use await here
}
Instead of try catch we can do something like this
try {
User.findOne({ email: email }).then((response)=>{
//do something
});
} catch (err) {
//do something
}

How can I restrict the ability to post and save data to the database to only the admin in node.js?

In the users table, I have two collections, one of which is admin and the other which is not.
Now I only want admin user to post data.
Here is the post request:
router.post("/bus/add", auth, async (req, res) => {
const bus = new Bus(req.body);
const user = await User.find({ admin: true });
try {
if (user) {
await bus.save();
res.status(201).send(bus);
} else {
return res.status(401).send("You are not allowed to perform this action");
}
} catch (e) {
res.status(500).json({
message: "Please enter the valid data",
});
}
});
I'm using JWT to determine whether or not the user is an admin. I've set one of the users' admin roles to 'true' in the user schema.
Authentication middleware:
const authentication = async (req, res, next) => {
try {
const token = req.header("Authorization").replace("Bearer ", "");
const decoded = jwt.verify(token, process.env.JWT_SECRET_KEY);
const user = await User.findOne({ _id: decoded._id, "tokens.token": token });
if (!user) {
throw new error();
}
req.token = token
req.user = user
next();
} catch (e) {
res.status(401).send(e);
}
};
However, even non-admin users can post data, which is then saved to the database.
I want to restrict this.
I'm not sure how I can prevent non-admin users from posting data.
You need to check if the user is admin in the Auth middleware.
const authentication = async (req, res, next) => {
try {
const token = req.header('Authorization').replace('Bearer ', '');
const decoded = jwt.verify(token, process.env.JWT_SECRET_KEY);
const user = await User.findOne({
_id: decoded._id,
'tokens.token': token,
admin: true
});
if (!user) {
throw new error();
}
req.token = token;
req.user = user;
next();
} catch (e) {
res.status(401).send(e);
}
};
And remove the line const user = await User.find({ admin: true }); and related if check in the route.
router.post("/bus/add", auth, async (req, res) => {
const bus = new Bus(req.body);
try {
await bus.save();
res.status(201).send(bus);
} catch (e) {
res.status(500).json({
message: "Please enter the valid data",
});
}
});

When I try to get post data from the MongoDB through postman, I get this cannot set header error

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);
}
});

Mongoose middleware schema.pre('save', ...)

I am making a REST API in NodeJS using the Mongoose Driver. I want to hash the passwords before saving them. For the same, I am using Mongoose Schema, where I made a userSchema for my user model. And for hashing I used the following function.
userSchema.pre('save', async (next) => {
const user = this;
console.log(user);
console.log(user.isModified);
console.log(user.isModified());
console.log(user.isModified('password'));
if (!user.isModified('password')) return next();
console.log('just before saving...');
user.password = await bcrypt.hash(user.password, 8);
console.log('just before saving...');
next();
});
But on creating a user or modifying it I am getting Error 500, and {} is getting returned. My routers are as follows.
router.post('/users', async (req, res) => {
const user = User(req.body);
try {
await user.save();
res.status(201).send(user);
} catch (e) {
res.status(400).send(e);
}
});
router.patch('/users/:id', async (req, res) => {
const updateProperties = Object.keys(req.body);
const allowedUpdateProperties = [
'name', 'age', 'email', 'password'
];
const isValid = updateProperties.every((property) => allowedUpdateProperties.includes(property));
if (!isValid) {
return res.status(400).send({error: "Invalid properties to update."})
}
const _id = req.params.id;
try {
const user = await User.findById(req.params.id);
updateProperties.forEach((property) => user[property] = req.body[property]);
await user.save();
if (!user) {
return res.status(404).send();
}
res.status(200).send(user);
} catch (e) {
res.status(400).send(e);
}
});
And the following is my console output.
Server running on port 3000
{}
undefined
On commenting out the userSchema.pre('save', ...) everything is working as expected. Please can you help me figure out where am I going wrong.
Using function definition instead of arrow function for mongoose pre save middleware:
userSchema.pre('save', async function(next) { // this line
const user = this;
console.log(user);
console.log(user.isModified);
console.log(user.isModified());
console.log(user.isModified('password'));
if (!user.isModified('password')) return next();
console.log('just before saving...');
user.password = await bcrypt.hash(user.password, 8);
console.log('just before saving...');
next();
});
Update:
The difference is this context, if you use arrow function in this line const user = this;, this now is your current file (schema file, I guess).
When you use function keyword, this context will belong to the caller object (user instance).

deleting key from object but still showing in response

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);
}

Resources