node.js - bcrypt.compare() is not working in async function - node.js

Here is my code:
async function(account, pwd, done) {
Member.findOne({ account: account }, (err, user) => {
if (err) {
return done(err);
} else if (await bcrypt.compare(pwd, user.pwd)) {
return done(null, user);
} else {
return done(null, false, { message: 'Password incorrect' });
}
});
}
But when I run the server, I always get this error:
E:\nodeJS\Express\server\routes\api\login.js:22
} else if (await bcrypt.compare(pwd, user.pwd)) {
^^^^^^
SyntaxError: Unexpected identifier
Does anyone know how to fix it?
Thank you for answering!

Try this:
const bcrypt = require('bcrypt');
bcrypt.compareSync(rawPassword, hashedPassword);
compareSync works for async functions without an await.

I found the solution! Just change the location of async.
Here is the solution:
function(account, pwd, done) {
Member.findOne({ account: account }, async(err, user) => {
if (err) {
return done(err);
} else if (await bcrypt.compare(pwd, user.pwd)) {
return done(null, user);
} else {
return done(null, false, { message: 'Password incorrect' });
}
});
}
Put async in front of (err, user) =>......
Thank again for those who spend time on my question!

Related

Cannot set headers after they are sent to the client Error while regenerating sessions

I'm facing with the "Cannot set headers after they are sent to the client" Error in Express/Passportjs.
I do know that this error mainly occurs when callbacks has called twice, or more;,
but I don't understand why this error has occured. I've specified Error cases on the bottom.
Here's the code that I've wrote in app.js for the strategy setup
passport.use(new KakaoStrategy(
{
clientID: kakao_key,
callbackURL: '/userSchemaAPI/login/kakao/callback',
},
async (accessToken, refreshToken, profile, done) => {
try {
const foundUser = await User.findOne(
{
snsId: profile.id,
provider: 'kakao',
},
);
if (foundUser) {
return done(null, foundUser);
}
else{
return done(null, false, profile);
}
} catch (error) {
return done(error);
}
},
),
);
this is the router code
router.get('/login/kakao', passport.authenticate('kakao'));
router.get('/login/kakao/callback', (req, res, next) => {
passport.authenticate('kakao', function (err, user, info){
if (err) {
return next(err);
}
if (!user) {
const { id } = info;
req.session.joinUser = {
snsId: id,
email: info._json.kakao_account.email,
username: info._json.properties.nickname,
};
return req.session.save(() => {
res.redirect('/userSchemaAPI/register/kakao');
});
}
return req.login(user, function (error){
if (error) {
return next(error);
}
return res.redirect('/');
});
})(req, res, next);
});
I try to register new user when user hasn't registered in our DB. here's the
code when POST request has called in routes '/userSchemaAPI/register/kakao' -
module.exports.createNewKakaoUser = async(req,res,next)=>{
try {
const { snsId, username, email } = req.session.joinUser;
const user = await User.create({
provider : 'kakao',
snsId : snsId,
email: email,
username: req.body.username || username,
});
req.session.regenerate(() => {
req.login(user, (error) => {
if (error) {
return next(error);
}
return res.redirect('/');
});
});
} catch (error) {
console.error(error);
next(error);
}
}
There's 2 major Errors that I am currently facing with.
I can't directly logIn when my info is already in DB.
I succeeded to register my Info in DB, but server makes an "Cannot set headers after they are sent to the client" Error while logging in.
Your code will continue later after next is handled if you don't return
req.session.regenerate(() => {
req.login(user, (error) => {
if (error) {
return next(error);
}
return res.redirect('/');
});
});

Can I deserialize the different users like student and admin in passport.js?

In passport js authentication when the deserialize the user I have the following doubt?
Can I use different Login tables to fetch the user ?(depends on they are admin or student like that)
But I was try to implement it by using if statement that was won't work it which mean cannot fetch from db. What I am doing that any help !
Using LocalStrategy u can use it. Here you have to make two different calls to check.
Sample:
passport.use(new LocalStrategy(
function(username, password, done) {
async.parallel([
function(cb){
User.findOne({ username: username }, function (err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false); }
if (!user.verifyPassword(password)) { return done(null, false); }
return cb(null, user);
});
},
function(cb){
Admin.findOne({ username: username }, function (err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false); }
if (!user.verifyPassword(password)) { return done(null, false); }
return cb(null, user);
});
}
], ([res1, res2]) => {
// on result
done(null, res1)
// OR
done(null, res1)
})
}
));

passport local strategy's not getting called

I'm currently making app with vue & express.
I adopted passport as authentication library and I've used it several times.
so I know that when I make login request, passport middleware authenticates user by 'xx strategy'.
The problem is register request works well but login isn't.
I constantly get 'false' for response.data.
I can't even guess where the false comes from.
There is no return statements that returns false.
follow is my code.
passport.deserializeUser((authId, done) => {
let hasUser
let users = User.findOneByAuthId(authId)
.then(results => (hasUser = results.length))
if (hasUser) {
console.log('deserialize!')
done(null, users[0])
} else {
console.log('no user')
done(null, { message: 'no user' })
}
})
passport.use(new LocalStrategy(
(username, password, done) => {
let hasUser
let users = User.findOneByEmail(username)
.then(results => (hasUser = results.length))
if (hasUser) {
let user = users[0]
let { hash } = user
if (User.checkPassword(password, hash)) {
done(null, { message: 'success' })
} else {
done(null, { message: 'wrong password' })
}
} else {
done(null, { message: 'wrong email' })
}
}
))
router.post('/register', (req, res) => {
if (!fun.emailChecker(req.body.username)) {
return res.status(403).json({
message: 'Invalid Email'
})
}
if (!fun.passwordChecker(req.body.password)) {
return res.status(403).json({
message: 'Invalid Password'
})
}
let hasUser
User.findOneByEmail(req.body.username)
.then(results => (hasUser = results.length))
if (hasUser) {
return res.status(409).json({
message: 'Email Exist'
})
} else {
let user = {
authId: 'local: ' + req.body.username,
email: req.body.username,
hash: User.hashPassword(req.body.password),
displayName: req.body.displayName
}
User.create(user)
.then(results => {
if (!results) {
throw new Error('user creation error')
} else {
req.login(user, err => {
if (!err) {
req.session.save(() => {
return res.json({ success: true })
})
}
})
}
})
}
})
router.post('/login', (req, res) => {
passport.authenticate('local', (err, result) => {
if (!err) {
return res.json(result)
}
})(req, res)
})
// vue component
methods: {
onSubmit () {
axios.post('http://localhost:3001/auth/login', {
email: this.email,
password: this.password
}).then(response => console.log(response.data))
},
There are various issues with your code.
Starting with incorrect promise handling:
let hasUser
let users = User.findOneByAuthId(authId)
.then(results => (hasUser = results.length))
if (hasUser) { ... }
You are trying to make asynchronous code synchronous here. The code that depends on the result of the asynchronous query has to be moved to inside the then handler (both in deserializeUser and the strategy verification handler):
User.findOneByAuthId(authId).then(users => {
let hasUser = users.length;
if (hasUser) {
console.log('deserialize!')
done(null, users[0]);
} else {
console.log('no user')
done(Error('no user'));
}
});
(I'm not quite sure why your method findOneByAuthId, whose name implies that there will be at most one result, could result in an array)
Also, you're not using the correct convention for passing user data and login errors back to Passport:
if (User.checkPassword(password, hash)) {
done(null, { message: 'success' })
} else {
done(null, { message: 'wrong password' })
}
This should look like this (and obviously, other places where done is called incorrectly should be fixed too):
if (User.checkPassword(password, hash)) {
done(null, user);
} else {
done(null, false, { message: 'wrong password' })
}
(documented here under "Verify Callback")
Finally, you're using passport.authenticate() incorrectly, which is probably the cause of the false being returned. Instead, try this:
router.post('/login', passport.authenticate('local'), (req, res) => {
return res.json(req.user); // or whatever you want to return in case of login success
})
(documented here; if you want to use a custom callback, look for "Custom Callback" on that page)

Using async/await with done()/next() middleware functions

I'm starting to use async/await. Generally, what is a pattern to use await with middleware done/next functions?
For example, how could I replace .then() in the code below with await? localAuthenticate is done/next middleware. Do I need to make a separate async function to use await inside it?
I'd like something like this (even better w/o the try/catch):
function localAuthenticate(User, email, password, hostname, done) {
try { // where is async?
// Find user
let user = await User.findOne({ email: email.toLowerCase() }).exec()
if(!user) return done(null, false, { message: 'This email is not registered.' });
// Test password
user.authenticate(password, function(authError, authenticated) {
if(authError) return done(authError);
if(!authenticated) return done(null, false, { message: 'This password is not correct.' });
return done(null, user);
});
} catch(err) { done(err); }
}
Original code from Passport.js authentication middleware:
function localAuthenticate(User, email, password, hostname, done) {
User.findOne({
email: email.toLowerCase()
}).exec()
.then(user => {
if(!user) {
return done(null, false, {
message: 'This email is not registered.'
});
}
user.authenticate(password, function(authError, authenticated) {
if(authError) {
return done(authError);
}
if(!authenticated) {
return done(null, false, { message: 'This password is not correct.' });
} else {
return done(null, user);
}
});
})
.catch(err => done(err));
}
await can only be called within an async function - see the MDN documentation
Your function needs to be async function localAuthenticate(User, email, password, hostname, done).
The try/catch is the way to catch exceptions when using await, instead of the .then/.catch you are used to when dealing with Promises directly.
Your function would approximate, when using async/await:
async function localAuthenticate(User, email, password, hostname, done) {
try {
// Find user
let user = await User.findOne({ email: email.toLowerCase() }).exec()
if (!user) {
return done(null, false, { message: 'This email is not registered.' })
}
user.authenticate(password, function (authError, authenticated) {
if (authError) {
return done(authError)
}
if (!authenticated) {
return done(null, false, { message: 'This password is not correct.' });
}
return done(null, user);
})
} catch (err) {
done(err)
}
}
Further reading:
http://rossboucher.com/await/#/
https://ponyfoo.com/articles/understanding-javascript-async-await

How to implements two strategies with passportjs

I have two model in my node/express app :
User model (simple user authenticated)
Manager model (user with different rights and different fields)
I want to use passport to authenticated them :
User with couple : Username / Password
Manager with couple : Email / Password
I correctly implemented PassportJS for the User model but now I try to do the same for Manager model.
exports.postlogin = function(req, res, next) {
passport.authenticate('user', function(err, user, info) {
if (err) { return next(err) }
if (!user) {
req.session.messages = [info.message];
return res.redirect('/login')
}
req.logIn(user, function(err) {
if (err) { return next(err); }
return res.redirect('/');
});
})(req, res, next);
};
exports.postlogin = function(req, res, next) {
passport.authenticate('manager', function(err, manager, info) {
if (err) { return next(err) }
if (!manager) {
req.session.messages = [info.message];
return res.redirect('/manager_signup')
}
req.logIn(manager, function(err) {
if (err) { return next(err); }
return res.redirect('/');
});
})(req, res, next);
};
The two strategies :
passport.use('user', new LocalStrategy(function(username, password, done) {
UserModel.findOne({ username: username }, function(err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false, { message: 'Unknown user ' + username }); }
user.comparePassword(password, function(err, isMatch) {
if (err) return done(err);
if(isMatch) {
return done(null, user);
} else {
return done(null, false, { message: 'Invalid password' });
}
});
});
}));
passport.use('manager', new LocalStrategy({usernameField: 'manager_signin_email', passwordField: 'manager_signin_password'},function(manager_signin_email, manager_signin_password, done) {
ManagerModel.findOne({ email: manager_signin_email }, function(err, manager) {
if (err) { return done(err); }
if (!manager) { return done(null, false, { message: 'Unknown manager ' + manager_signin_email }); }
manager.comparePassword(manager_signin_password, function(err, isMatch) {
if (err) return done(err);
if(isMatch) {
console.log('Manager login OK : ' + manager_signin_email);
return done(null, manager);
} else {
return done(null, false, { message: 'Invalid password' });
}
});
});
}));
The problem is for Serialize/Deserialize.
For User I have this :
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
UserModel.findById(id, function (err, user) {
done(err, user);
});
});
But I don't know how to do for Manager model.
You could maybe do something like this when serializing:
if (isUser(user)) {
// serialize user
done(null, "user_"+user.id );
} else if (isManager(user)) {
// serialize manager
done(null, "manager_"+user.id );
}
And then check the prefix when deserializing.
I think there is an open issue for what you want (https://github.com/jaredhanson/passport/issues/148) .
Alternatively you could change you serialize method to include information if it's user or manager not only user id and when deserializing read that info and load user/manager from proper model.
In case anyone is still stumbling upon this. You can check the type of the object you are serializing. Note all there of by objects are generalized in to a PrincipleInfo object.
function PrincipleInfo(principleId, principleType, details) {
this.principleId = principleId;
this.principleType = principleType;
this.details = details;
}
passport.serializeUser(function (userObjectThing, done) {
//userObjectThing could be a User or a Sponsor
var principleType = "user";
var userPrototype = Object.getPrototypeOf(userObjectThing);
if (userPrototype === User.prototype) {
principleType = "user";
} else if (userPrototype === Sponsor.prototype) {
principleType = "sponsor";
} else if (userPrototype === Admin.prototype) {
principleType = "admin";
}
var principleInfo = new PrincipleInfo(userObjectThing.id, principleType, '');
done(null,principleInfo);
});
passport.deserializeUser(function (principleInfo, done) {
if (principleInfo.principleType == 'user') {
User.findOne({
_id: principleInfo.principleId
}, '-salt -hashedPassword', function (err, user) { // don't ever give out the password or salt
done(err, user);
});
} else if (principleInfo.principleType == 'sponsor') {
Sponsor.findOne({
_id: principleInfo.principleId
}, '-salt -hashedPassword', function (err, user) { // don't ever give out the password or salt
done(err, user);
});
} else if (principleInfo.principleType == 'admin') {
Admin.findOne({
_id: principleInfo.principleId
}, '-salt -hashedPassword', function (err, user) { // don't ever give out the password or salt
done(err, user);
});
}
});

Resources