Can I create a user object inside password hash function? - javascript

I was following a Backend REST Api tutorial, and in the video, this is what he did, creating a user object, then changing newUser.password to the hash generated.
// Data is valid, register user
let newUser = new User({
name,
username,
password,
email,
});
// Hash password
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(newUser.password, salt, (err, hash) => {
if (err) throw err;
newUser.password = hash;
newUser.save().then(user => {
return res.status(201).json({
success: true,
msg: "User is now registered"
})
})
})
})
Why not just do it all at once?
// Why not do it in one go instaed of creating and then changing User?
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(password, salt, (err, hash) => {
if (err) throw err;
let newUser = new User({
name,
username,
hash,
email,
});
newUser.save().then(user => {
return res.status(201).json({
success: true,
msg: "User is now registered"
})
})
})
})
Is there something wrong with doing it together?

since bcrypt takes a callback function your hash is only gonna be available between the brackets for the callback function, which is why you do the assignment between those brackets. since you declare newuser between those brackets then newuser isn't available in the greater scope

Related

Mongodb error: Cannot set headers after they are sent to the client

I am basically creating a user registration form where I check if the submitted passwords match and then check if the user already exists by querying the collection to see if the submitted username or email already exist. If all of the data passed the checks then I create I new user. My issue is that if the username or email already exist then the user is still created. Shouldn't returning a status if a user is found stop the function?
Front end submission:
submitNewUser() {
axios.post('http://localhost:5000/api/settings/users', {
name: this.newUser.name,
username: this.newUser.username,
email: this.newUser.email,
password: this.newUser.password,
confirm_password: this.newUser.confirm_password,
role: this.newUser.role
})
.then(() => {
this.getUsers();
})
.catch(error => {
console.log(error);
})
}
The user has to be signed in to create a new user so I user passport check if token contains a valid user
passport authentication check:
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
const User = require('../models/User');
const key = require('./keys').secret;
const opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = key;
module.exports = passport => {
passport.use(
new JwtStrategy(opts, (jwt_payload, done) =>{
User.findById(jwt_payload._id).then(user => {
if (user) return done(null, user);
return done(null, false);
}).catch(err => {
console.log(err);
});
})
);
};
End route:
router.post('/users', passport.authenticate('jwt', {
session: false
}), (req, res) => {
let companyId = req.user.company_id;
let {
name,
username,
email,
password,
confirm_password,
role,
} = req.body;
//Check that passwords match
if( password !== confirm_password ) {
return res.status(400).json({
msg: "Passwords do not match"
})
}
//Check for unique username
User.findOne({ username: username })
.then(user => {
console.log('username')
if(user) {
return res.status(400).json({
msg: "Username is already taken."
});
}
})
.catch(error => console.log(error));
//check for unique email
User.findOne({ email: email })
.then(user => {
console.log('email')
if(user) {
return res.status(400).json({
msg: "Email is already registered. Did you forget your password?"
});
}
})
.catch(error => console.log(error));
let newUser = new User({
name,
username,
password,
email,
user_role: role,
company_id: companyId,
});
// Hash password
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(newUser.password, salt, (err, hash) => {
if(err) throw err;
newUser.password = hash;
newUser.save()
.then(user => {
return res.status(201).json({
success: true,
msg: "User is now registered."
});
})
.catch(error => console.log(error));
});
});
});
This is an error that I get if the user already exists:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:482:11)
at ServerResponse.header (C:\Users\Reece\OneDrive\Desktop\Fyber Docs\Valentis-Pipeline-MEVN-App\node_modules\express\lib\response.js:771:10)
at ServerResponse.send (C:\Users\Reece\OneDrive\Desktop\Fyber Docs\Valentis-Pipeline-MEVN-App\node_modules\express\lib\response.js:170:12)
at ServerResponse.json (C:\Users\Reece\OneDrive\Desktop\Fyber Docs\Valentis-Pipeline-MEVN-App\node_modules\express\lib\response.js:267:15)
at User.findOne.then.user (C:\Users\Reece\OneDrive\Desktop\Fyber Docs\Valentis-Pipeline-MEVN-App\server\routes\api\settings.js:106:40)
at processTicksAndRejections (internal/process/next_tick.js:81:5)
Due to the async nature, (req, res) => { is returning before any of your calls to mongodb are either succeeding or failing, yet you still use the res object to send a response back to the client.
I don't know exactly how to deal with this in the framework you're using, but the framework appears to populate res's header with data and send it back to the client before you start to modify the res object.

Update user information

Tried this for updating user information , only phone number but it's not getting update.
router.post('/edit', checkAuth, function (req, res, next) {
console.log(req.userData.userId)
User.update({_id: req.userData.userId}, {$set:req.userData.phoneNo}, function (err){
if (err) {
console.log(err);
}
res.status(200).send(req.userData);
});
});
My user controller const mongoose = require ('mongoose');
const User = mongoose.model('User');
module.exports.register = (req, res, next) =>{
var user = new User();
user.fullName = req.body.fullName;
user.email = req.body.email;
user.password = req.body.password;
user.phoneNumber = req.body.phoneNumber;
user.save((err, doc) =>{
if(!err)
res.send(doc);
else{
if (err.code == 11000)
res.status(422).send(["Entered duplicate email address. Please check"]);
else
return next(err);
}
});
}
And then I am authenticating by passing jwt on this field
phoneNo: user[0].phoneNumber
The auth-token verifies and decode the fields
const token = req.headers.authorization.split(" ")[1];
const decoded = jwt.verify(token, process.env.JWT_KEY)
req.userData = decoded;
Update is not working and getting error message Invalid atomic update value for $set. Expected an object, received string .
first of all, you should use PATCH-method - because you are updating only one item in existed object, in body you should send id of user and new value of certain value. If you use mongoose you can try it
User.findOneAndUpdate({ _id: id }, updatedItem, { new: true }, (err, doc) => {
if (err) return res.send(err.message)
if (doc) return res.send(doc);
})
const id = req.body._id;, if you dont use mongoose you should try findAndModify method
Your code
User.update({_id: req.userData.userId}, {$set:req.userData.phoneNo}
Correct code:
User.update({_id: req.userData.userId}, {$set:{phoneNumber:req.userData.phoneNo}}
Try this method:
User.findByIdAndUpdate(req.userData.userId, { $set:{phoneNumber:req.userData.phoneNo}}, { new: true }, function (err, user) {
if (err) console.log(err);
res.send(user);
});

Cannot add jwt token to node.js response

I'm new to javascript ecosystem and want to add jwt token to response out of this registration router:
router.post('/register', (req, res)=> {
User.findOne({email: req.body.email})
.then(user => {
if(user) {
return res.status(400).json({error: 'Email already exists'});
} else {
const newUser = new User({
username: req.body.username,
email: req.body.email,
password: req.body.password
});
bcrypt.genSalt(10, (err, salt)=> {
bcrypt.hash(newUser.password, salt, (err, hash)=> {
if (err) throw err;
newUser.password = hash;
newUser.save()
.then(user => res.status(200).json(user)) //<=Problem is here
.catch(err => console.log(err));
} )
})
}
})
});
The jwt snippet (which works fine on longin router) is this:
const payload = {
username: user.username
}
//sign token
jwt.sign(
payload,
keys.secretOrKey,
{ expiresIn: 3600},
(err, token)=> {
res.json({
success: true,
token: 'Bearer '+ token,
username: username
});
});
The problem is that I don't know how can I add the snippet to the response header.
When I add it after .then(user => I get a SyntaxError: Unexpected token const error.
How can I make it?
Sounds like you didn't wrap the jwt snippet within curly braces. Without them the arrow function where the problem appears only takes one expression. Paste the jwt snippet into the following snippet instead.
bcrypt.genSalt(10, (err, salt)=> {
bcrypt.hash(newUser.password, salt, (err, hash)=> {
if (err) throw err;
newUser.password = hash;
newUser.save()
.then(user => {
res.status(200).json(user);
<JWT_Snippet_here>
}
.catch(err => console.log(err));
})
})
Here you can see how the syntax of arrow functions is defined. The following quote shows the most important part.
(param1, param2, …, paramN) => { statements }
(param1, param2, …, paramN) => expression
Curly braces are needed in order to be able to use a list of statements. The error you experienced occurred because your JavaScript engine expected a single expression but instead found a list of statements.

Password doesn't hash before being saved into the db

I'm developing an app using Node.js, Mongoose, MongoDb, Express.
I'm trying to hash the password before being saved in the db when the user register, but it doesn't seems to work. The password is saved without hashing, any suggestions?
'use strict'
let mongoose = require('mongoose')
let bcrypt = require('bcrypt-nodejs')
var Schema = mongoose.Schema
var userSchema = Schema({
name: { type: String, required: true, unique: true },
password: { type: String, required: true },
createdAt: {
type: Date,
require: true,
default: Date.now
}
})
// check if user already exists
userSchema.path('name').validate(function (name) {
User.findOne({name}, function (err, user) {
if (err) {
console.log('error')
} if (user) {
console.log('The user already exists')
console.log(user)
}
})
}, 'The user already exists')
// password validation
userSchema.path('password').validate(function (password) {
return password.length >= 6
}, 'The password must be of minimum length 6 characters.')
var User = mongoose.model('User', userSchema)
// hashing and adding salt pre save()
userSchema.pre('save', function (next) {
bcrypt.genSalt(10, function (err, salt) {
if (err) {
return next(err)
}
bcrypt.hash(this.password, salt, null, function (err, hash) {
// Store hash in your password DB.
if (err) {
return next(err)
}
// set the password to the hash
this.password = hash
})
next()
})
})
module.exports = User
Its because you do next() before bcrypt.hash callback is called. Move next() into bcrypt.hash callback.
userSchema.pre('save', function(next) {
bcrypt.genSalt(10, function(err, salt) {
if (err) {
return next(err)
}
bcrypt.hash(this.password, salt, null, function(err, hash) {
// Store hash in your password DB.
if (err) {
return next(err)
}
// set the password to the hash
this.password = hash
next()
})
})
})
next() should be called within bcrypt.hash() method when using callbacks.
For synchronous:
userSchema.pre('save', (next) => {
const salt = bcrypt.genSaltSync(10)
const hash = bcrypt.hashSync(this.password, salt)
this.password = hash
next()
})

Mongoose changes password every time I save with pre-save hook

I'm using a pre-save hook with bcrypt to encrypt passwords on the system. It works fine when creating or changing a password. The problem is that it seems to re-encrypt the password every time I change and save a different field, for example e-mail.
Probably easier to explain with code. Here's the model:
const UserSchema = new Schema({
email: {
type: String,
required: true,
lowercase: true,
unique: true,
trim: true
},
password: {
type: String,
required: true
}
})
And the hook:
UserSchema.pre('save', function(next){
const user = this;
console.log(user);
bcrypt.genSalt(10, function(err, salt){
if (err){ return next(err) }
bcrypt.hash(user.password, salt, null, function(err, hash){
if(err){return next(err)}
user.password = hash;
next();
})
})
});
And here's my code to update the e-mail address:
module.exports = function(req, res){
User.findOne({ _id: req.body.user}, function(err, doc){
if(err){
console.log(err);
return;
}
doc.email = req.body.data;
doc.save(function(err, returnData){
if (err){
console.log(err);
return;
}
res.send(returnData);
})
})
}
So when I call doc.save in the final example, it updates the e-mail address as intended but it also re-encrypts the password, meaning if the user then logs out, they can't log back in again.
Can anyone help with how to get around this?
Try this:
UserSchema.pre('save', function(next){
if (!this.isModified('password')) return next();
const user = this;
bcrypt.genSalt(10, function(err, salt){
if (err){ return next(err) }
bcrypt.hash(user.password, salt, null, function(err, hash){
if(err){return next(err)}
user.password = hash;
next();
})
})
});
OK, I managed to figure it out - just needed a little bit of conditional logic in the pre-save hook:
UserSchema.pre('save', function(next){
if(!this.isModified('password')){
return next();
} // Adding this statement solved the problem!!
const user = this;
bcrypt.genSalt(10, function(err, salt){
if (err){ return next(err) }
bcrypt.hash(user.password, salt, null, function(err, hash){
if(err){return next(err)}
user.password = hash;
next();
})
})
});

Categories