Node.js: crypto.pbkdf2 password to hex - javascript

I currently use following set up to register new users:
// creates a new user
app.post('/users', function(req, res) {
// create new user
var user = new User();
// assign post
user.username = req.body.username;
user.email = req.body.email;
crypto.randomBytes(32, function(err, buf) {
if (err) throw err;
user.salt = buf.toString('hex');
crypto.pbkdf2(req.body.password, user.salt, 25000, 512, function(err, encodedPassword) {
if (err) throw err;
user.password = (encodedPassword.toString('hex')); // this line
user.save(function(err, user) {
if (!err) return res.send(err, 500);
return res.json(user);
});
}.bind(this));
});
});
Take a closer look at this line:
user.password = (encodedPassword.toString('hex'));
This should encode the password string (which looks like a binary one) into a hex string.
For some reason this doesn't work.
Why not?
Byside:
What encoding is recommand for salt and password storage (hex, binary, base64)?

It appears that if it's already a String, the toString('hex') won't work.
What I did was something like Buffer(encodedPassword, 'binary').toString('hex').

Related

bcryptjs compare function returns false when passwords contain numbers

I have used bcryptjs to hash my passwords and the registration of the user takes place fine.
But when I try to login, the bcrypt.compare function returns a false promise if the passwords contain numbers or other characters.
Everything works fine if the password is just letters.
I have searched everywhere but could not find a solution for this error?
Here are my files :
users.js (MODEL)
var mongoose = require("mongoose");
var bcrypt = require("bcryptjs");
var userSchema = mongoose.Schema({
name: String,
email: String,
password: String,
products: [{
type: mongoose.Schema.Types.ObjectID,
ref: "Product"
}],
username: String
});
var User = module.exports = mongoose.model("User", userSchema);
module.exports.createUser = function(newUser, callback) {
bcrypt.genSalt(5,function(err,salt){
bcrypt.hash(newUser.password,salt,function(err,hash){
newUser.password = hash;
newUser.save(callback);
});
});
}
module.exports.comparePassword = function(candidatePassword, hash, callback){
bcrypt.compare(candidatePassword, hash, function(err, isMatch) {
if(err) throw err;
console.log(isMatch);
callback(null, isMatch);
});
}
module.exports.getUserByUsername = function(username, callback){
var query = {username: username};
User.findOne(query, callback);
}
app.js
app.use(passport.initialize());
app.use(passport.session());
var LocalStrategy = require("passport-local").Strategy;
passport.use(new LocalStrategy(
function(username, password, done) {
User.findOne({username:username}, function(err, user){
if(err) throw err;
if(!user) {
return done(null,false, {message: 'Unknown user'});
}
User.comparePassword(password,user.password,function(err, isMatch){
if(err) {
console.log("ERR1");
return done(err);
}
if(isMatch) {
console.log("MATCH");
return done(null,user);
} else {
console.log("NOT VALID");
return done(null, false, {message: 'Invalid password'});
}
});
});
}
));
passport.serializeUser(function(user,done){
done(null,user.id);
});
passport.deserializeUser(function(id,done){
User.getUserById(id, function(err,user){
done(err,user);
})
})
users.js (ROUTE)
router.post("/login", passport.authenticate('local', function (error, user, info){
if(error) {
console.error(error);
console.log('Failed login:');
}
if (user === false) {
console.log("user not found");
} else {
// handle successful login ...
console.log("logged in");
}
}), function (req, res) {
res.send(req.user);
});
router.post("/signup", function (req, res) {
console.log(req.body);
var password = req.body.password;
var password2 = req.body.password2;
if(password == password2) {
var newUser = new User ({
name: req.body.name,
email: req.body.email,
username: req.body.username,
password: req.body.username
});
User.createUser(newUser, function(err, user){
if(err)
throw err;
console.log(user);
res.send(user).end();
});
} else {
res.status(500).send("{errors: \"Passwords don't match\"}").end()
}
});
Whenever I enter a password that contains numbers, I get
false
NOT VALID
user not found
I'm sorry if I have done or not done something extremely simple. This is my first time using bcryptjs.
All answers are appreciated!
In the users.js file at the comparePassword function inside the compare method of bcrypt you are throwing the error instead of passing it to the callback. Maybe if you make this change:
module.exports.comparePassword = function(candidatePassword, hash, callback){
bcrypt.compare(candidatePassword, hash, function(err, isMatch) {
console.log(isMatch);
callback(err, isMatch);
});
}
You will see what kind of error it's generated by the case when you introduce numbers in your passwords.

Bcrypt hash function returns undefined result

In a controller I'm trying to create a hash and save it with the user, using this section of code.
router.post('/', (req, res)=>{
console.log(req.body.password, process.env.SALT_ROUNDS);
bcrypt.hash(req.body.password, process.env.SALT_ROUNDS, function (err, hash) {
if(err) {
res.status(500);
}
console.log("hash ", hash, req.body.password);
// await req.context.models.User.create({
// username: req.body.username,
// email: req.body.email,
// password: hash })
// .then(function(data) {
// console.log("user saved")
// if (data) {
// res.send(data);
// }
// });
});
});
But the result of the callback function inside hash argument is undefined. Here are the logs.
asd123 10
hash undefined asd123
Where is the problem, that hash is undefined ?
It turned out that process.env.SALT_ROUNDS was a string and I didn't quite understood that from the returned result. When I parse it to number +process.env.SALT_ROUNDS it worked.
asd123 10
hash $2b$10$Ty3JNeIBXUizrS85MsH8H.oTNsRtXzdiv9ZdamxnG7CmPD2CjSZG2 asd123

Reset password function with bcrypt does not work, Cannot set headers after they are sent to the client?

So i made a reset password function and it looks like this:
router.get('/reset/:id',function(req,res,next){
User.findOne({'resetToken': req.params.id.trim()})
.exec(function(error, user){
if (error) return next(error);
else if(!user){
var err = new Error('Invalid Reset Link! The system is unable to find a user with the given reset code.');
err.status = 401;
return next(err);
}else return res.render('resetpass',{token: req.params.id});
})
})
router.post('/reset/:id', function(req,res,next){
if(req.body.newPassword === req.body.confirmNewPassword)
User.findOne({'resetToken': req.params.id.trim()},function(error,user){
if(error) return next(error);
bcrypt.hash(req.body.password, 5, function(err, hash) {
if (err) {
return next(err);
}
user.password = hash;
user.resetToken ='';
user.save();
})
});
return res.redirect('/login');
})
so when i complete the reset password fields and click submit i get this in console : Cannot set headers after they are sent to the client
and if i use this:
router.post('/reset/:id', function(req,res,next){
if(req.body.newPassword === req.body.confirmNewPassword)
var cp;
User.findOne({'resetToken': req.params.id.trim()},function(error,user){
if(error) return next(error);
bcrypt.hash(req.body.password, 5, function(err, hash) {
if (err) {
return next(err);
}
cp = hash;
})
user.password = cp;
user.resetToken ='';
user.save();
});
return res.redirect('/login');
})
my user.password is empty and i get this : Cannot set property 'password' of null -> becouse i set that in UserSchema.
So why is that ? i tried with console.logs in other function just with variables and is something like that :
bcrypt.hash(.... {console.log('a') })
console.log('b');
my console looks like this :
b
a
So bcrypt console.log is after. What can i do ?
Please note that User.findOne() is an async operation.
The redirection res.redirect('/login') should be called after new password is saved.
You could try this code below:
router.post('/reset/:id', function (req, res, next) {
if (req.body.newPassword === req.body.confirmNewPassword) {
User.findOne({
'resetToken': req.params.id.trim()
}, function (error, user) {
if (error) return next(error);
bcrypt.hash(req.body.password, 5, function (err, hash) {
if (err) {
return next(err);
}
user.password = hash;
user.resetToken = '';
user.save();
// Do the redirection here
res.redirect('/login');
})
});
}
// Do nothing here
})

"this" is not refering the the user object in mongoose model

I have the following code to save user object to database from express,
api.post('/signup', function (req, res) {
var user = new User();
user.name = req.body.name;
user.email = req.body.email;
user.setPassword(req.body.password);
user.save(function (err) {
err ? res.send(err) : res.json({ message: 'User Created!'})
})
})
and below here for the user schema,
var mongoose = require('mongoose');
var bcrypt = require('bcrypt');
var SALT_WORK_FACTOR = 10;
var Schema = mongoose.Schema;
var userSchema = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
unique: true,
required: true
},
password: String,
})
userSchema.methods.setPassword = function (password) {
bcrypt.genSalt(SALT_WORK_FACTOR, function (err, salt) {
if (err) return console.log(err);
bcrypt.hash(password, salt, function (err, hash) {
if (err) return console.log(err);
this.password = hash; // <= `this` will not be saved to mongoDB
})
})
}
module.exports = mongoose.model('User', userSchema);
When it performed the save function, it will show that the password was undefined and will save the object to mongodb without password value.
I've checked the this question as well and changed all my functions to not using arrow method but it still got the same error.
The same issue when i was using middleware hook that this reference was not referring to user object. Below here for my another approach,
userSchema.pre('save', (next) => {
bcrypt.genSalt(SALT_WORK_FACTOR, function (err, salt) {
if(err) return next(err);
bcrypt.hash(this.password, salt, function (err, hash) {
if (err) return next(err);
this.password = hash; // <= `this` will not be saved to mongoDB
next();
})
})
})
Any idea for this value to be saved to database when perform save?
You should try below code, Hope it will work for you:
userSchema.methods.setPassword = function (password) {
var pswd=password;
bcrypt.genSalt(SALT_WORK_FACTOR, function (err, salt) {
if (err) return console.log(err);
bcrypt.hash(pswd, salt, function (err, hash) {
if (err) return console.log(err);
pswd = hash;
console.log(pwsd); // your decripted password
})
})
}
I will elaborate more in the next days (I want to test a couple of things) but the error, I think, is because the meaning of this in JS is tricky.
Basically this is not a reference to the function's lexical scope, it is binding done in the context where the function is called.
So, as a fast solution while I elaborate more, I would recommend you to encrypt the password in:
user.setPassword(encrypt(req.body.password)); and simplify the mongoose's setPassword function
Hope that helps.
If you want this to refer to the userSchema context, you can use arrow functions for the bcrypt callbacks. Arrow functions will not create a new function scope, so it will preserve the original context.
userSchema.methods.setPassword = function (password) {
bcrypt.genSalt(SALT_WORK_FACTOR, (err, salt) => {
if (err) return console.log(err);
bcrypt.hash(password, salt, (err, hash) => {
if (err) return console.log(err);
this.password = hash; // <= `this` will be the user object
})
})
}
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions

Compare passwords BcryptJS

So I'm trying to build a very basic user login. I'm trying to create a user, then login with those credentials and get back a JSON Web Token. Where I'm stuck is trying to compare the passwords then send a response.
Steps:
Create User:
enter email and password
salt/hash user password
store user into database
return success
Login
find user by request email value
if found compare passwords
passwords good send JSON Web Token
User Model
email:{
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
}
User Routes
var express = require('express');
var router = express.Router();
var jwt = require('jsonwebtoken');
var bcrypt = require('bcryptjs');
// Create User
...
bcrypt.genSalt(10, function(err, salt) {
bcrypt.hash("superSecret", salt, function(err, hash) {
user.password = hash;
user.save();
res.json({success: true, message: 'Create user successful'});
});
});
...
// Login
...
bcrypt.compare(req.body.password, 'superSecret', function(err, res) {
if(req.body.password != user.password){
res.json({success: false, message: 'passwords do not match'});
} else {
// Send JWT
}
});
So the two problems here is that, I can't send a response nor can I compare the password. Just completely stuck on this, any help would be greatly appreciated.
As described in the doc, you should use bcrypt.compare like that:
bcrypt.compare(req.body.password, user.password, function(err, res) {
if (err){
// handle error
}
if (res) {
// Send JWT
} else {
// response is OutgoingMessage object that server response http request
return response.json({success: false, message: 'passwords do not match'});
}
});
And here is a nice post about Password Authentication with Mongoose (Part 1): bcrypt
//required files
const express = require('express')
const router = express.Router();
//bcryptjs
const bcrypt = require('bcryptjs')
//User modal of mongoDB
const User = require('../../models/User')
//Post request for login
router.post('/', (req, res) => {
//email and password
const email = req.body.email
const password = req.body.password
//find user exist or not
User.findOne({ email })
.then(user => {
//if user not exist than return status 400
if (!user) return res.status(400).json({ msg: "User not exist" })
//if user exist than compare password
//password comes from the user
//user.password comes from the database
bcrypt.compare(password, user.password, (err, data) => {
//if error than throw error
if (err) throw err
//if both match than you can do anything
if (data) {
return res.status(200).json({ msg: "Login success" })
} else {
return res.status(401).json({ msg: "Invalid credencial" })
}
})
})
})
module.exports = router
If we you to use bcryptjs in browser(HTML) then you can add bcryptjs CDN to do this.
CDN - https://cdn.jsdelivr.net/npm/bcryptjs#2.4.3/dist/bcrypt.js
Example-
HTML- (Add above CDN in tag)
JS-
var bcrypt = dcodeIO.bcrypt;
/** One way, can't decrypt but can compare */
var salt = bcrypt.genSaltSync(10);
/** Encrypt password */
bcrypt.hash('anypassword', salt, (err, res) => {
console.log('hash', res)
hash = res
compare(hash)
});
/** Compare stored password with new encrypted password */
function compare(encrypted) {
bcrypt.compare('aboveusedpassword', encrypted, (err, res) => {
// res == true or res == false
console.log('Compared result', res, hash)
})
}
If you want to do same in Nodejs
/** Import lib like below and use same functions as written above */
var bcrypt = require('bcryptjs')
From what I can see your logic is correct.
If you are using mongoose I suggest you to use the pre 'save' hook.
User Schema
userSchema.pre('save', function(next) {
// only hash the password if it has been modified (or is new)
if (!this.isModified('password')) {
return next();
}
// generate a salt
return bcrypt.genSalt(10, function(error, salt) {
if (error) {
return next(error);
}
// hash the password using the new salt
return bcrypt.hash(this.password, salt, function(error, hash) {
if (error) {
return next(error);
}
// override the cleartext password with the hashed one
this.password = hash;
return next();
});
});
});
userSchema.methods.comparePassword = function(passw, cb) {
bcrypt.compare(passw, this.password, function(err, isMatch) {
if (err) {
return cb(err, false);
}
return cb(null, isMatch);
});
};
And in your routes:
Login
...
return user.comparePassword(password, function(error, isMatch) {
var payload = {
iat: Math.round(Date.now() / 1000),
exp: Math.round((Date.now() / 1000) + 30 * 24 * 60),
iss: 'Whatever the issuer is example: localhost:3000',
email: user.email
};
var token = jwt.encode(payload, 'secret');
if (isMatch && !error) {
// if user is found and password is right create a token
return res.json({
success: true,
token: `JWT ${token}`,
user: user,
msg: 'Authentication was succesful'
});
}
return next({code: 401, msg: 'Password is incorrect'});
});
});
Create user
// Pre hook will take care of password creation
return user.save()
.then(function(user) {
var payload = {
iat: Math.round(Date.now() / 1000),
exp: Math.round((Date.now() / 1000) + 30 * 24 * 60),
iss: 'Whatever the issuer is example: localhost:3000',
email: user.email
};
var token = jwt.encode(payload, 'secret');
return res.status(201).json({user, token: `JWT ${token}`, msg: 'User was succesfully created'});
})
.catch((err) => next(err));
bcrypt.compare(req.body.password, user.password, function(err, results){
if(err){
throw new Error(err)
}
if (results) {
return res.status(200).json({ msg: "Login success" })
} else {
return res.status(401).json({ msg: "Invalid credencial" })
}
})
const bcrypt = require("bcryptjs");
const salt = bcrypt.genSaltSync(10);
const hashPassword = (password) => bcrypt.hashSync(password, salt);
const comparePassword = (password, hashedPassword) =>
bcrypt.compareSync(password, hashedPassword);
bcrypt.compare(req.body.password, user.password)
.then(valid => {
if (!valid) {
return res.status(401).json({ message: 'Paire login/mot de passe incorrecte' });
}
res.status(200).json({
userId: user._id,
token:jwt.sign(
{userId: user._id},
process.env.ACCESS_TOKEN_SECRET_KEY,
{expiresIn:'24h'}
),
message: 'connected'
});
})
.catch(error => res.status(500).json({ error }));
enter code here

Categories

Resources