Issue with nodejs bcrypt when using keyword this - node.js

I am new to nodejs but I am trying to use it as server for an online game I am designing. I come across the tutorial and I am just playing around with the code to see how I can modify it to suit me need.
https://github.com/scotch-io/easy-node-authentication/tree/linking
1 thing I couldn't solve however is while using bcrypt, my code below is trying to isolate the issue:
//check-hash
var bcrypt = require('bcrypt-nodejs');
var salt = bcrypt.genSaltSync(8);
var newAccount = {
username: "admin",
password: "1235",
email: "test#domain.com",
AccCreateOn: Date.now(),
LastLogin: Date.now()
}
newAccount.generateHash = function(password) {
return bcrypt.hashSync(password, salt);
};
// checking if password is valid
newAccount.validPassword = function(password) {
return bcrypt.compareSync(password, this.password);
};
console.log (newAccount.password);
newAccount.password = newAccount.generateHash(this.password);
console.log(newAccount.validPassword("1235")); //false
console.log(newAccount.validPassword("1236"));//false
I already figure out the line at fault is
return bcrypt.compareSync(password, this.password);
which if I change this.password to newAccount.password it will work just fine.
what is wrong as I thought this.password in this context is newAccount.password, or is it not?

Related

Nodejs Mongoose Bcrypt when i change hash password i cant login back

When i renew password I cant login back. giving response password is not match error but if I dont renew my password I can login.
Step 1 (Register)
Customer.findOne({$or:[{email:user.email},{username:user.username}]},function(err,data){
if(!data){
var tokencreator = generateUUID();
var customer = new Customer({
userid:uuidv4(),
username:req.body.username,
email:req.body.email,
password:hashpass(req.body.password),
token:tokencreator,
registerdate:new Date(),
lastlogin:new Date()
})
customer.save()
Database You can see work register.
Step 2 (Renew Password)
app.post('/passnewer', function(req,res){
console.log(req.body)
Customer.findOne({passtoken:req.body.token},function(err,data){
if(data){
if(req.body.password != data.username){
if(req.body.password = comparepass(req.body.password,data.password)){
res.send({"Success":"Your New Password Cannot Be The Same As Your Old Password!","redirect":"false"});
}
else{
Customer.findOne({passtoken:req.body.token},function(err,data){
data.password = hashpass(req.body.password);
data.save();
});
res.send({"Success":"Password Renewal Successful You Are Redirected!","redirect":"true"});
}
}
else{
res.send({"Success":"Your password cannot be the as your username!","redirect":"false"});
}
}
else{
res.send({"Denied":"İnvalid token!"});
}
})
})
Response You can see database password changed.
Database
Step 3 (Login Account) when I try to log in, it gives error.
app.post('/userlogin', function(req,res){
Customer.findOne({email:req.body.email},function(err,data){
if(data){
if(data.password = comparepass(req.body.password,data.password)){
if(data.status != "Active"){
res.send({"Success":"Email verification need!"})
}
else{
req.session.isLoggedIn = true;
req.session.userID = data.userid;
Customer.findOne({userid:req.session.userID},function(err,data){
data.lastlogin = new Date();
data.save()
});
res.send({"Success":"Login Success","redirect":"true"});
}
}else{
res.send({"Success":"Password error!"});
}
}else{
res.send({"Success":"E-Mail error!"});
}
});
})
Response
Schema My schema
var mongoose = require("mongoose")
var Schema = mongoose.Schema;
var customerSchema = new Schema({
userid:String,
username:String,
email:String,
password:String,
address:String,
registerdate:String,
tickets:Array,
token:String,
passtoken:String,
status: {
type: String,
enum: ['Pending', 'Active'],
default: 'Pending'
},
lastlogin:{
type: String,
default: 'TBD'
},
})
var Customer = mongoose.model('Customer',customerSchema)
module.exports = Customer
hash and compare functions Bcrypt functions
function hashpass(passnohash){
return bcrypt.hashSync(`${hashprefix}${passnohash}`, saltRounds)
}
function comparepass(passnohash, passhash){
return bcrypt.compareSync(`${hashprefix}${passnohash}`, passhash)
}
I would console.log the new password hash getting saved to the database and then console.log the hash when you try to log back in (in step 3). See whether they're the same or not.
Then have a look at these lines in particular:
In POST /userlogin request handler:
if(data.password = comparepass(req.body.password,data.password)){
In POST /passnewer request handler:
if(req.body.password = comparepass(req.body.password,data.password)){
Both of the above lines make assignments (not comparisons). So it seems like you overwrite the password with the return value of comparepass (comparepass seems to return a boolean).
This may be a potential bug (if I'm reading things correctly), since req.body.password seems to get overwritten with true/false, meaning you save the hash of true/false (and not the hash of the user's new password) when you call data.save().
(If comparepassword returns a boolean, you can use its return value directly within the if statement. No need to compare it to data.password or req.body.password.)
In route POST /passnewer' you overwrite req.body.passwordbyif(req.body.password = comparepass(req.body.password,data.password))`
Now your new password will become true (or false), then you hash and save this password to your record in the database.
if(comparepass(req.body.password,data.password))
That enough, why you always assign a variable in if condition?

Error with async await ( at least in my opinion ) - ExpressJS, MongoDB, NodeJS, multer

This is my code in backend (ExpressJs, multer)
Note: I used cors in server.js file and it working as I expected in previous route, but in this Register Admin route, the other error occurs
const multer = require('multer');
const upload = multer({ dest: './public/uploads/' });
const User = require('../models/user.model'); // User model
router.post('/admin/register', upload.single('avatar'),
async (req, res) => {
const avatar = req.file.path.slice(7);
const user = {
...JSON.parse(req.body.user),
avatar
};
// hapi/joi validate => 1
const { error } = Validation.adminRegisterValidation(user);
if (error) {
return res.json({ error: error.details[0].message });
}
// validate from database => 2
const emailExist = await User.findOne({ email: user.email });
if (emailExist) {
return res.json({ error: 'Email already exist!' });
}
// hash the password => 3
const hashedPassword = await bcrypt.hash(user.password, 10);
const newUser = new User({
...user,
admin: false,
password: hashedPassword,
sub_admin: true
});
try {
await newUser.save();
return res.json("Add user success!");
} catch (err) {
return res.json({ error: err });
}
}
);
If i turn 1,2,3 to comment, the user is added as I expected
This project in my Github Github
- Front-end in file: src -> components -> dashboard -> user-dashboard -> CreateUser.js
- Backend in file: backend -> routes -> user.route.js
THIS IS MY ERROR IMAGE
Well I found two errors that might be causing your issue, not really sure if it will totally fix your issue, but will definitely get you close.
1) Remove Max length property from user schema
Right now you have max length of 30 for password in your user Schema but once you hash the password, hashed password can go beyond 30 characters.
You can click here to see what I'm talking about.
2) You need to remove old password in order to store the new hashed password
In this line of code you are using Spread Syntax to include all the fields in NewUser object from user object, which already includes password that comes from your frontend
const newUser = new User({
...user,
admin: false,
password: hashedPassword,
sub_admin: true
});
This what causes newUser to have 2 password fields, to delete the old password after hashing do this
// hash the password => 3
const hashedPassword = await bcrypt.hash(user.password, 10);
delete user.password
const newUser = new User({
...user,
admin: false,
password: hashedPassword,
sub_admin: true
});
This way you can set password to new Hashed one.
AS your error is very Generic It's almost impossible to DEBUG it, these are the few things I found very odd.
As I said not really sure if it will fix your error but it might get you close

Bcrypt-NodeJS compare() returns false whatever the password

I know that question has already been asked a few times (like here, here or there, or even on Github, but none of the answers actually worked for me...
I am trying to develop authentication for a NodeJS app using Mongoose and Passport, and using Bcrypt-NodeJS to hash the users' passwords.
Everything was working without any problem before I decided to refactor the User schema and to use the async methods of bcrypt. The hashing still works while creating a new user but I am now unable to verify a password against its hash stored in MongoDB.
What do I know?
bcrypt.compare() always returns false whatever the password is correct or not, and whatever the password (I tried several strings).
The password is only hashed once (so the hash is not re-hashed) on user's creation.
The password and the hash given to the compare method are the right ones, in the right order.
The password and the hash are of type "String".
The hash isn't truncated when stored in the database (60 characters long string).
The hash fetched in the database is the same as the one stored on user's creation.
Code
User schema
Some fields have been stripped to keep it clear, but I kept the relevant parts.
var userSchema = mongoose.Schema({
// Local authentication
password: {
hash: {
type: String,
select: false
},
modified: {
type: Date,
default: Date.now
}
},
// User data
profile: {
email: {
type: String,
required: true,
unique: true
}
},
// Dates
lastSignedIn: {
type: Date,
default: Date.now
}
});
Password hashing
userSchema.statics.hashPassword = function(password, callback) {
bcrypt.hash(password, bcrypt.genSaltSync(12), null, function(err, hash) {
if (err) return callback(err);
callback(null, hash);
});
}
Password comparison
userSchema.methods.comparePassword = function(password, callback) {
// Here, `password` is the string entered in the login form
// and `this.password.hash` is the hash stored in the database
// No problem so far
bcrypt.compare(password, this.password.hash, function(err, match) {
// Here, `err == null` and `match == false` whatever the password
if (err) return callback(err);
callback(null, match);
});
}
User authentication
userSchema.statics.authenticate = function(email, password, callback) {
this.findOne({ 'profile.email': email })
.select('+password.hash')
.exec(function(err, user) {
if (err) return callback(err);
if (!user) return callback(null, false);
user.comparePassword(password, function(err, match) {
// Here, `err == null` and `match == false`
if (err) return callback(err);
if (!match) return callback(null, false);
// Update the user
user.lastSignedIn = Date.now();
user.save(function(err) {
if (err) return callback(err);
user.password.hash = undefined;
callback(null, user);
});
});
});
}
It may be a "simple" mistake I made but I wasn't able to find anything wrong in a few hours... May you have any idea to make that method work, I would be glad to read it.
Thank you guys.
Edit:
When running this bit of code, match is actually equal to true. So I know my methods are correct. I suspect this has something to do with the storage of the hash in the database, but I really have no idea of what can cause this error to occur.
var pwd = 'TestingPwd01!';
mongoose.model('User').hashPassword(pwd, function(err, hash) {
console.log('Password: ' + pwd);
console.log('Hash: ' + hash);
user.password.hash = hash;
user.comparePassword(pwd, function(err, match) {
console.log('Match: ' + match);
});
});
Edit 2 (and solution) :
I put it there in case it could be helpful to someone one day...
I found the error in my code, which was occurring during the user's registration (and actually the only piece of code I didn't post here). I was hashing the user.password object instead of user.password.plaintext...
It's only by changing my dependencies from "brcypt-nodejs" to "bcryptjs" that I was able to find the error because bcryptjs throws an error when asked to hash an object, while brcypt-nodejs just hashes the object as if it were a string.
I know the solution has been found but just in case you are landing here out of google search and have the same issue, especially if you are using a schema.pre("save") function, sometimes there is a tendency of saving the same model several times, hence re-hashing the password each time. This is especially true if you are using references in mongoDB to create schema relationship. Here is what my registration function looked like:
Signup Code
User.create(newUser, (err, user) => {
if (err || !user) {
console.warn("Error at stage 1");
return res.json(transformedApiRes(err, "Signup error", false)).status(400);
}
let personData: PersonInterface = <PersonInterface>{};
personData.firstName = req.body.first_name;
personData.lastName = req.body.last_name;
personData.user = user._id;
Person.create(personData, function (err1: Error, person: any): any {
if (err1 || !person) {
return res.json(transformedApiRes(err1, "Error while saving to Persons", false));
}
/* One-to-One relationship */
user.person = person;
user.save(function (err, user) {
if (err || !user) {
return res.json({error: err}, "Error while linking user and person models", false);
}
emitter.emit("userRegistered", user);
return res.json(transformedApiRes(user, `Signup Successful`, true));
});
});
});
As you can see there is a nested save on User because I had to link the User model with Person model (one-to-one). As a result, I had the mismatch error because I was using a pre-save function and every time I triggered User.create or User.save, the function would be called and it would re-hash the existing password. A console statement inside pre-save gave me the following, showing that indeed that password was re-hashed:
Console debug after a single signup call
{ plain: 'passwd',
hash: '$2b$10$S2g9jIcmjGxE0aT1ASd6lujHqT87kijqXTss1XtUHJCIkAlk0Vi0S' }
{ plain: '$2b$10$S2g9jIcmjGxE0aT1ASd6lujHqT87kijqXTss1XtUHJCIkAlk0Vi0S',
hash: '$2b$10$KRkVY3M8a8KX9FcZRX.l8.oTSupI/Fg0xij9lezgOxN8Lld7RCHXm' }
The Fix, The Solution
To fix this, you have to modify your pre("save") code to ensure the password is only hashed if it is the first time it is being saved to the db or if it has been modified. To do this, surround your pre-save code in these blocks:
if (user.isModified("password") || user.isNew) {
//Perform password hashing here
} else {
return next();
}
Here is how the whole of my pre-save function looks like
UsersSchema.pre("save", function (next: NextFunction): any {
let user: any = this;
if (user.isModified("password") || user.isNew) {
bcrypt.genSalt(10, function (err: Error, salt: string): any {
if (err) {
return next(err);
}
bcrypt.hash(user.password, salt, function (err: Error, hash: string) {
if (err) {
console.log(err);
return next(err);
}
console.warn({plain: user.password, hash: hash});
user.password = hash;
next();
});
});
} else {
return next();
}
});
Hopefully this helps someone.
I am dropping this here because it might help someone someday.
In my own case, the reason why I was having bcrypt.compare as false even when I supplied the right authentication details was because of the constraints on the datatype in the model. So each time the hash was saved in the DB, it was truncated in order to fit into the 50 characters constraints.
I had
'password': {
type: DataTypes.STRING(50),
allowNull: false,
comment: "null"
},
The string could only contain 50 characters but the result of bcrypt.hash was more than that.
FIX
I modified the model thus DataTypes.STRING(255)
bcrypt.hash() has 3 arguments... you have 4 for some reason.
Instead of
bcrypt.hash(password, bcrypt.genSaltSync(12), null, function(err, hash) {
it should be
bcrypt.hash(password, bcrypt.genSaltSync(12), function(err, hash) {
Since you were hashing only during user creation, then you might not have been hashing properly. You may need to re-create the users.
Tip: If you are switching
then().then()
Block always check return value.
You can always check the max length for the password field in the database. Make sure it is large. In my case, I have set it to 500. And then the code worked flawlessly!
TS version
const { phone, password } = loginDto;
const user = await this.usersService.findUserByPhone(phone);
const match = await compare(password, user.password);
if (user && match){
return user
}else{
throw new UnauthorizedException();
}
JS version
const { phone, password } = loginDto;
const user = await this.usersService.findUserByPhone(phone);
const match = await bcrypt.compare(password, user.password);
if (user && match){
return user
}else{
throw new UnauthorizedException();
}

How should I store salts and passwords in MongoDB

I am trying to store passwords and salts in MongoDB and I'm not sure which datatype should be used. When I use strings, the encrypted password appears to be stored correctly, but the generated salt, which is created with new Buffer(crypto.randomBytes(16).toString('base64'), 'base64');, seems to have characters that weren't recognized. For example, I have a salt stored as �y_�6j(�l~Z}0ۡ\" and I don't think this is correct.
Is the problem that it's stored as a string?
While registering a user, you can generate a hashed password using bcrypt. Let's call this password as P#1. Save this hashed password (P#1) in your database only, and not the salt.
While logging in a user, generate hashed version of the password which the user sends, let's call it P#2. Now you just have to match P# and P#2. If they match, the user is authenticated. This way you can perform authentication without actually saving the salt in your database.
I will try to put it in simple way with the help of an example.
// My user schema
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var bcrypt = require('bcrypt-nodejs');
var userSchema = new Schema({
username: String,
password: String
});
// hash the password
userSchema.methods.generateHash = function(password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
};
// checking if password is valid
userSchema.methods.validPassword = function(password) {
return bcrypt.compareSync(password, this.password);
};
var User = mongoose.model('user', userSchema);
module.exports = User;
// My APIs for registering and authenticating a user
var User = require('/path/to/user/model');
app.post('/register', function(req, res) {
var new_user = new User({
username: req.body.username
});
new_user.password = new_user.generateHash(req.body.password);
new_user.save();
});
app.post('/login', function(req, res) {
User.findOne({username: req.body.username}, function(err, user) {
if (!user.validPassword(req.body.password)) {
//password did not match
} else {
// password matched. proceed forward
}
});
});
Hope it helps you!
Ankit Gomkale's answer is correct (and IMHO clean!), but you might wonder how it's possible to verify the hashed password is the same as the input string being tested.
This is because the output of bcrypt.hashSync(password, bcrypt.genSaltSync(8), null); is not a "hashed password", it is a "hash string", of the form (source):
$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
\__/\/ \____________________/\_____________________________/
Alg Cost Salt Hash
Therefore, this string contains the password hash, but also the salt. Verification of a password (e.g. in bcrypt.compareSync(password, this.password);) will use this salt, not create a new random salt.

Bcrypt error: illegal arguments String unidentified

here is my complete code
var express = require('express'),
app = express(),
mongoose = require('mongoose'),
bodyParser = require('body-parser'),
morgan = require('morgan'),
webToken = require('jsonwebtoken'),
bcrypt = require('bcryptjs'),
assert = require('assert');
Schema = mongoose.Schema,
secretKey = "omjdiuwkslxmshsoepdukslsj";
//User Schema
var userSchema = new Schema({
username: {type: String, required: true, index: {unique:true}},
password: {type: String, required: true, select: false}
})
userSchema.pre('save', function(next){
var user = this;
if(!user.isModified('password')) return next();
bcrypt.hash(user.password, null, null, function(err, hash){
if(err) return next(err);
user.password = hash;
next();
})
});
userSchema.methods.comparePassword = function(password){
var user = this;
bcrypt.compare(password, user.password, function(err, result){
if(err){
console.log(err);
}
else {
console.log("passwords match!");
return;
}
})
}
var userModel = mongoose.model('users', userSchema);
//Connecting to Mongo
mongoose.connect("mongodb://localhost/userstories", function(err){
if(err) {
console.log(err);
}
else {
console.log("Connected to database!");
}
});
//Creating Token
function createToken(user){
var token = webToken.sign({
_id: user.id,
username: user.username
}, secretKey,{
expiresIn: 30 * 60 * 1000
})
return token;
}
//Middlewares
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
app.use(morgan('dev'));
//Api's
app.post('/signup', function(req, res){
var user = new userModel({
username: req.body.username,
password: req.body.password
})
user.save(function(err){
if(err){
console.log(err);
}
else{
res.json({message: "User created!"});
}
})
})
app.post('/login', function(req, res){
userModel.findOne({username: req.body.username}, function(err, user){
if(err) console.log(err);
if(!user){
res.send("User not found!");
}
else if(user){
var validPassword = user.comparePassword(req.body.password);
if(validPassword){
var tokens = createToken(user);
res.json({
success: true,
message: "Successfully logged In",
token: tokens
});
}
else {
res.send("Invalid password");
}
}
})
});
//Running the server
app.listen(3000, function(err){
if(err) console.log("port not working");
else{
console.log("Everything went just fine");
}
})
I've tried every approaches and saw all the answers here. But no one seem to come across this illegal argument error. Please figure this one out for me Im sure there is a bug I cant see
In your User Schema, you are setting select to false for the password field. This means that anytime you look for a user in the schema as you're trying to do in the login request, you won't get the value of the password field or any other field that has select false defined in the schema.
What you need to do is to specify you need the password when the user is found:
app.post('/login', function(req, res){
userModel.findOne({username: req.body.username}, 'password', function(err, user){
// continue
}
This will return only the _id and the password from the DB. If you want to return other fields, you'd have to add them in:
app.post('/login', function(req, res){
userModel.findOne({username: req.body.username}, 'password firstName lastName email', function(err, user){
// continue
}
I have tried the same code for authentication once and got the same error Error: Illegal arguments: string, function.
But I did not see any issue with the code. The thing is, I had registered two users with the same user name and a different password. Then, when I tried to log in with the user name and one password this error occurred and stopped the server.
So it seems that you are also facing the same problem. Make sure there are no errors with this stuff if you do not want to have a bug in your code.
Check the value of user.password before sending it to bcrypt.compare().
Chances are, you've fetched the user without including the password property, resulting in a value of undefined. This can happen in Sequelize if you set custom attributes or if you're using a scope that excludes props.
Your code is wrong in this place. You may see it.
var validPassword = user.comparePassword(req.body.password);
If you use bcryptjs thrid party plugins, like that
let validPassword = bcrypt.compare(req.body.password, user.password);
bcrypt.compare(password, hashedPassword);
In my particular case, I was dealing with this error, checking out all the code up and down unsuccessfully for almost two days.
Finally, realized that the column PASSWORD in MariaDB was in uppercase. Theoretically that shouldn't affect at all, but I decided to rename it to lowercase and bum! problem solved.
For those using async/await for database calls, make sure you don't forget the await keyword on the User.findOne() call.
In my case, I had forgotten the await keyword while fetching the user. This as a result, was giving me a Promise object instead of User object and hence the password property on it was undefined.
I also encountered the same error when I was using
bcrypt.compareSync("input to be compared with the hash", hash).
Later on I discovered that I was supposed to pass the actual value in the first input parameter i.e (The actual value from which the hash was generated) and the hash in the second input parameter, but I was passing hashed values in both the input parameters.
After correcting the same it was giving me the desired output as true or false.
You can also run and check your code here.
Do like this:
UserSchema.pre('save', async function (next) {
const hash = await bcrypt.hash(this.password, 10);
this.password = hash;
next()
})
You need to specify that you also want the password because you have set the select property to false on password.
So when you are fetching the user, just make sure to explicitly specify that you also want the password. Add .select('+password') on the user object when you are querying a user.
In my case it was a simple spelling mistake before sending to bcrypt.hash :
If you're testing this with Postman, I just found an issue where the default Content-Type header is set to text/plain. If you untick the default header (as it doesn't allow you to change it) and add another Content-Type header with a value of application/json, it works.

Resources