I'm trying to build a login page where I get the hashed password from mysql db using Sequelize and then calling bcrypt compare to dehash the password and compare it with the user's login input for authentication.
However, bcrypt compare is always executing slower than the return causing the value to always be "". I know this has to do with asynchronous behaviour but I don't know how to properly write this code to make it work.
authenticate: (req, res) => {
let userDetails = req.query;
User.findOne({
where: {
username: userDetails.username
}
})
.then((user) => {
// How can I make this so, correctPassword() finishes
// and then the authenticated variable will be either false or true?
let authenticated = correctPassword(userDetails.password, user.password);
return authenticated;
})
.then((authenticated) => {
// right now authenticated is "" in client side console.
res.send(authenticated);
})
.catch((error) => {
console.log('there was an error: ', error);
});
}
}
const correctPassword = (enteredPassword, originalPassword) => {
return bcrypt.compare(enteredPassword, originalPassword, (err, res) =>{
return res;
});
}
You're almost there. You correctly intuited that correctPassword executes asyncronously, though it is written as if it's syncronous.
First off, let's make correctPassword a promise, so we can use async/await or call .then on it
const correctPassword = (enteredPassword, originalPassword) => {
return new Promise(resolve => {
bcrypt.compare(enteredPassword, originalPassword, (err, res) =>{
resolve(res)
});
})
}
Next, you have two approaches to ensure the order of operations in your code executes correctly:
(Recommended) Use async/await syntax allowing us to write synchronous-looking code:
authenticate: async (req, res) => {
let userDetails = req.query;
try {
const user = await User.findOne({
where: {
username: userDetails.username
}
});
const authenticated = await correctPassword(userDetails.password, user.password);
res.send(authenticated);
} catch(e) {
res.status(400).send(e)
}
}
Continue using promises:
authenticate: (req, res) => {
let userDetails = req.query;
User.findOne({
where: {
username: userDetails.username
}
}).then(() => {
correctPassword(userDetails.password, user.password)
.then(authenticated => {
res.send(authenticated)
})
.catch(e => {
res.send(e)
})
})
}
You can't assign async function to variable which is used by sync code later on. If you want to do sync function, you can use await/aync. But in here I recommend you use promise for compare function as well.
User.findOne({
where: {
username: userDetails.username
}
})
.then((user) => {
return correctPassword(userDetails.password, user.password);
})
.then((authenticated) => {
res.send(authenticated);
})
Bcrypt also supports promise.
const correctPassword = (enteredPassword, originalPassword) => {
return bcrypt.compare(enteredPassword, originalPassword).then((res) =>{
return res;
});
}
Related
I'm currently buildung an application with nodejs (10.15.2) but I'm running into some problems with the login function.
Snippet from my "dbconnector.js" file:
login(email, password) {
return userModel.findOne({
email: email
}).lean().then(function(user) {
// return user;
return bcrypt.compare(password, user.password, function(err, res) {
if (res) {
console.log(user); //prints the userinfo - works fine
return user;
}
});
});
};
Snippet from my serve.js file:
app.post('/login', async (req, res) => {
var { email, password } = req.body;
var user = await dbconnector.login(email,password);
console.log(user) //returns undefined
if (user != false) {
console.log("loggedin");
const accessToken = jwt.sign({ email: user.email, id: user.id }, key.tokenKey);
res.json({
accessToken
});
} else {
res.send('Username or password incorrect');
}
});
My problem is that the login()-function returns undefined. However, when I change it to the following, it works perfect (just without checking the password...):
login(email, password) {
return userModel.findOne({
email: email
}).lean().then(function(user) {
return user;
});
};
So I know something's wrong with the bcrypt / promise part but I couldn't find a solution.
My Fellow brother, the problem lives in the fact that a promise will only return you another promise and the compare callback that you have there as its name says callback is not apromise. Lets make it a promise
login(email, password) {
return userModel.findOne({
email: email
}).lean().then(function(user) {
// return user;
return new Promise((resolve, reject) => {
return bcrypt.compare(password, user.password, (err, res) => {
if (err) {
return reject(err);
}
return resolve(res);
})
};
BUT!!! if by any means the bcrypt package u are using is this one
https://www.npmjs.com/package/bcrypt
then you dont event have to promisify the method, it has it as promise already
login(email, password) {
return userModel.findOne({
email: email
}).lean().then(function(user) {
return bcrypt.compare(password, user.password);
}).then(isEquals => {
if (isEquals) {
return true
}
return false
}).catch(error => {
throw error;
})
};
I've taken a look at Brad Traversy's User routes for DevConnector, a project he uses to teach Node.js. The code does not look very clean or self-explanatory enough in my opinion; take a look at the /register route for example - it's all written in one big block. I am wondering if wrapping promises within other promises can solve this issue.
Below is what my alternative looks like:
router.post('/register', (req, res) => {
const firstName = req.body.firstName;
const lastName = req.body.lastName;
const email = req.body.email;
const password = req.body.password;
const dateOfBirth = req.body.dateOfBirth;
buildUserIfNotExists(email, firstName, lastName, dateOfBirth)
.then(user => hashUserPassword(user, password, 10))
.then(user => saveUser(user))
.then(user => {
sendActivationLink(user);
res.json(user);
})
.catch(errors => {
if (errors.internalError) {
console.log(errors.internalError);
res.status(500).json({
internalError: 'An internal error occured.'
})
} else {
res.status(400).json(errors);
}
});
});
An example of promise wrapper as I see it would be:
function saveUser(user) {
const errors = {};
return new Promise((resolve, reject) => {
user
.save()
.then(user => resolve(user))
.catch(err => {
errors.internalError = err;
reject(errors);
})
});
}
So far I've got no issues with this approach, everything works as expected. Are there any downsides to this that I am missing? Is there any way to simplify this even further?
I am not very experienced with JavaScript but I found the following simplification; instead of:
.then(user => saveUser(user))
I can simply do:
.then(user => user.save())
Actually, after a few modifications, my code looks like this:
router.post('/register', (req, res) => {
const newUser = new User({
name: req.body.name,
hometown: req.body.hometown,
dateOfBirth: req.body.dateOfBirth,
email: req.body.email,
activationHash: nanoid()
});
ensureUserNotExists(newUser)
.then(() => hashUserPassword(newUser, req.body.password))
.then(() => newUser.save())
.then(() => {
sendActivationLink(newUser).then(() => res.json(newUser))
})
.catch(errors => {
if (errors.internalError) {
console.log(errors.internalError);
res.status(500).json({
internalError: 'An internal error occured.'
})
} else {
res.status(400).json(errors);
}
});
});
How would I structure a function that has multiple Mongoose.findOne() nested in each other?
I need to do something like
const userId = '...';
const postId = '...';
const imageId = '...';
User.findById(userId).then(user => {
if (!user) {
return res.status(400).json({
status: 'error',
err: 'User not found',
});
}
Post.findById(postId).then(post => {
if (!post) {
return res.status(400).json({
status: 'error',
err: 'Post not found',
});
}
Image.findById(imageId).then(image => {
if (!image) {
return res.status(400).json({
status: 'error',
err: 'Image not found',
});
// DO SOMETHING WITH VARIABLES 'user', 'post', AND 'image'
}).catch(err => { .. });
}).catch(err => { .. });
}).catch(err => { .. });
Since Collection.findById() returns a promise, I guess I should use chaining instead of this structure.
So it might be something like
User
.findById(userId)
.then(user => Post.findById(postId))
.then(post => Image.findById(imageId))
.then(image => {
// DO SOMETHING WITH VARIABLES 'user', 'post', AND 'image'
});
.catch(err => { .. });
but I don't know how to access the variables user, post, and image, and how to throw the errors, so I can access them in my catch statement.
Edit
I have tried this
async function getPostAsync() {
const userId = '597989c668189f31483ffdbf';
const postId = '597989c62624ea74750c74f8';
if (!userId) {
throw new Error('User id missing');
}
if (!postId) {
throw new Error('Post id missing');
}
const user = await User.findById(userId);
const post = await Post.findById(postId);
return post;
}
app.get('/', (req, res) => {
getPostAsync().then(post => {
res.json({
status: 'success',
});
}).catch(err => {
res.status(400).json({
status: 'error',
err
});
})
});
but I just receive
{
"status": "error",
"err": {}
}
Am I doing something wrong?
But I get the same result even with
async function getPostAsync() {
throw new Error('msg');
return Post.find();
}
so I might be calling the async function wrong.
You can use Promise.all:
Promise.all([
User.findById(userId),
Post.findById(postId),
Image.findById(imageId)
])
.then(result)=>{
let user = result[0];
let post = result[1];
let image = result[2];
})
.catch(err => { .. });
Or with destructing assignment:
Promise.all([
User.findById(userId),
Post.findById(postId),
Image.findById(imageId)
])
.then(([user, post, image])=>{...})
.catch(err => { .. });
You can't access those variables inside a later promise's then, but you can get round it by assigning the local resolved values to global variables
let globalUser, globalPost; // create variables for later
User
.findById(userId)
.then(user => {
globalUser = user; // assign to global
return Post.findById(postId)
})
.then(post => {
globalPost = post; // assign to global
return Image.findById(imageId)
})
.then(image => {
// DO SOMETHING WITH VARIABLES 'globalUser', 'globalPost', AND 'image'
})
.catch(err => {... });
EDIT: or when using async/await:
async function() {
const user = await User.findById(userId);
const post = await Post.findById(postId);
const image = await Image.findById(imageId);
// do something with user, post and image
}
Seeing as your promises don't rely on each other you could also use Promise.all() in an async function:
async function() {
const result = await Promise.all([
User.findById(userId),
Post.findById(postId),
Image.findById(imageId)
]);
const [user, post, image] = result;
// do something with user, post and image
}
EDIT 2: Error handling
async function getImage() {
let user;
try {
user = await User.findById(userId);
} catch (error) { // deal with rejection of `User.findById`
// do something with error
}
// if these fail the entire function will throw
const post = await Post.findById(postId);
const image = await Image.findById(imageId);
return image;
}
getImage()
.then(image => {... })
.catch(error => {... }); // deal with rejection of `getImage` as a whole
The above code showcases the ways you can handle errors in an async function. The first is how we deal with an error in the User.findById function, by simply wrapping it in a try catch block.
The second method is by simply letting the entire async function throw an error. I.e. if the Post.findById or Image.findById promises reject, the entire getImage() promise will reject, which you can deal with in the .catch() handler.
I asked a question about JS Promises in this post:
I'm doing Promises wrong... What am I missing here?
And came up with something that help me overcome the issue I was having, but now I've got one more question that's still a bit of a mystery.
In the updated code I have:
login.ts:
import { Router } from 'express-tsc';
import { db, dbUserLevel } from '../../util/db';
import * as bodyParser from 'body-parser';
import { genToken } from '../../util/token';
import * as jwt from 'jsonwebtoken';
export var router = Router();
let urlencodedParser = bodyParser.urlencoded({ extended: false });
let jsonParser = bodyParser.json();
router.post('/', jsonParser, (req, res) => {
req.accepts(['json', 'text/plain']);
let data = req.body;
console.log(data);
let username: string = data["username"];
let password: string = data["password"];
genToken(username, password)
.then(token => {
res.status(200).send(token);
})
.catch(err => {
res.status(500).send(err);
});
});
The issue I'm now having is described in the commented code of the snippet below:
token.ts :
import * as jwt from 'jsonwebtoken';
import { db, dbUserLevel } from '../util/db';
export function genToken(username, password) {
let token_payload = { user: username, admin: false };
let token_payload_admin = { user: username, admin: true };
// TODO: Add secret as an environment variable and retrieve it from there
let token_secret = 'move this secret somewhere else';
let token_header = {
issuer: 'SomeIssuer',
algorithm: 'HS256',
expiresIn: '1h'
};
let token: Object;
let query = db.open()
.then(() => dbUserLevel('user'))
// If above is successful, this .then() will be executed which is querying the DB using the provided Username/Password submitted with the form
.then(() => db.collection('users').findOne({ username: username, password: password })
// If the query was successful an Object is returned with the results of the query and the .then() below is executed to analyze the result
.then((result) => {
if (result.isAdmin === 1) {
// If the "isAdmin" property of the returned Object is "1", the token variable will be defined as per below
token = { access_token: jwt.sign(token_payload_admin, token_secret, token_header) }
} else if (result.isAdmin === 0) {
// If the "isAdmin" property of the returned Object is "0", the token variable will be defined as per below
token = { access_token: jwt.sign(token_payload, token_secret, token_header) }
}
})
// The question is here... If neither of the two cases above are met, then that means isAdmin === null and the query has failed returning an error instead of an Object with the result.
// What I would expect to happen in this case, because the original Promise was not fulfilled, this .catch() should be called.
// Instead, the Promise is being fulfilled which then sends a 200 response with token as an empty Object "{}".
// How do I get this .catch() to reject the Promise and send the 500 response instead?
.catch(err => {
db.close();
Promise.reject(err);
}))
.then(() => {
db.close();
Promise.resolve(token);
return token;
})
.catch(err => {
db.close();
Promise.reject(err);
return err;
});
return query;
};
Your problem is that you missed to return the Promise.reject(…)s from your callbacks. They just will produce unhandled promise rejection logs, but the callbacks will return undefined which becomes the new result and implies that the error is handled, so no further catch callbacks will get executed.
However, that code should be simplified a lot anyway. Regarding the closing of the database connection, you should have a look at the disposer pattern or a finally method.
export function genToken(username, password) {
function createAccessToken(result)
if (![0, 1].includes(result.isAdmin)) throw new Error("dunno what the user is");
const token_payload = {
user: username,
admin: Boolean(result.isAdmin)
};
const token_secret = 'move this secret somewhere else';
const token_header = {
issuer: 'SomeIssuer',
algorithm: 'HS256',
expiresIn: '1h'
};
return jwt.sign(token_payload, token_secret, token_header);
}
return db.open()
.then(() => dbUserLevel('user'))
.then(() => db.collection('users').findOne({ username: username, password: password }))
.then(result => ({access_token: createAccessToken(result)}))
.then(token => {
db.close();
return token;
}, err => {
db.close();
throw err;
});
}
I've got a route using Sequelize.js
app.get('/api/users/:username', (req, res) => {
const foundUser = getUserByUsername(req.params.username);
console.log(`foundUser = ${foundUser}`);
return res.send(foundUser);
});
the getUserByUsername function is as follows
const getUserByUsername = username => {
Viewer.findOne({
where: {username}
}).then(response => {
console.log(response.dataValues);//the object with the data I need
return response.dataValues;
});
};
I hoped on getting the object in my const foundUser in my route, but it seems I need to wait until the findOne has been executed, because in my console I can see that the log of foundUser (which is undefined then) is executed before the function getUserByUsername
foundUser = undefined
Executing (default): SELECT `id`, `username`, `instakluiten`, `role`, `createdAt`, `updatedAt` FROM `viewers` AS `viewer` WHERE `viewer`.`username` = 'instak' LIMIT 1;
{ id: 19,
username: 'instak',
instakluiten: 18550,
role: 'moderators',
createdAt: 2016-10-02T16:27:44.000Z,
updatedAt: 2016-10-09T10:17:40.000Z }
How can I make sure that my foundUser will be updated with the data áfter the user has been found?
You have to return the promise that Sequelize creates and then wait for it to resolve. So the getUserByUsername becomes:
const getUserByUsername = username => {
return Viewer.findOne({
where: {username}
}).then(response => {
console.log(response.dataValues);//the object with the data I need
return response.dataValues;
});
};
and in the route:
app.get('/api/users/:username', (req, res) => {
getUserByUsername(req.params.username).then(foundUser => {
res.send(foundUser);
});
});
This is because you need to keep the chain of promises. If you forget to return it, the function returns undefined end even if the promise is finallly resolved, the value it resolves to never gets up back in the chain.
app.get('/api/users/:username', (req, res) => {
getUserByUsername(req.params.username, function(err, result){
const foundUser = result;
console.log(`foundUser = ${foundUser}`);
res.send(foundUser);
});
});
const getUserByUsername = function(username, callback) {
Viewer.findOne({
where: {username}
}).then(response => {
console.log(response.dataValues);//the object with the data I need
return callback(null, response.dataValues);
});
};
You can avoid it with promise or with callback
app.get('/api/users/:username', (req, res) => {
getUserByUsername(req.params.username, function(err, foundUser) {
if (!err) {
console.log(`foundUser = ${foundUser}`);
return res.send(foundUser);
} else {
res.send(err)
}
});
});
const getUserByUsername = (username, callback) => {
Viewer.findOne({
where: {
username
}
}).then(response => {
console.log(response.dataValues); //the object with the data I need
return callback(null, response.dataValues);
});
};