I am running tests for signin controller, it keeps giving the wrong status code (401) instead of 200 as i programmed it to be.
I expect it to use the data stored when a user signs up and return it if the given input is correct.
It works perfectly in postman but as i am writing tests, it throws the 401 error.
It is like it does not find the user
This is test block for the sign in:
it('it should signin a new user', (done) => {
request(app)
.post('/api/users/signin')
.send({
username: "Charles",
password: "challenger",
})
.expect(200)
.end((err, res) => {
if (err) {
return done(err);
}
done()
});
});
This is my controller for Logging in:
signin(req, res) {
const username = req.body.username.toLowerCase().trim();
// const email = req.body.email.trim();
if(!username) {
return res.status(401)
.send(
{status: false,
message: "Username cannot be empty"
});
}
else if (!req.body.password) {
return res.status(401)
.send({
status: false,
message: "Password field cannot be empty"
});
}
return User.findOne({
where: {
username,
}
})
.then((user) =>{
if(!user) {
return res.status(401).send({message: "User is not registered"})
}
else if(!user.validPassword(req.body.password)){
return res.status(401)
.send({
message: "The password is incorrect"
})
}
const token = user.generateAuthToken();
res.header('x-auth', token).status(200).send({
statusCode: 200,
message: `Welcome back, ${user.username}`,
user
});
})
.catch(error => {return res.status(400).send(error)})
},
This is the error i get:
1) Testing API routes POST /api/users/ it should signin a new user:
Error: expected 200 "OK", got 401 "Unauthorized"
at Test._assertStatus (node_modules\supertest\lib\test.js:266:12)
at Test._assertFunction (node_modules\supertest\lib\test.js:281:11)
at Test.assert (node_modules\supertest\lib\test.js:171:18)
at Server.assert (node_modules\supertest\lib\test.js:131:12)
at emitCloseNT (net.js:1552:8)
at _combinedTickCallback (internal/process/next_tick.js:77:11)
at process._tickCallback (internal/process/next_tick.js:104:9)
I would put a bunch of console.log() in there to see exactly which code is firing since you have 4 chances to fire the 401.
Here is some code for you to examine:
// I don't understand enough context, so I have to re-write this
// to show you how it could be an async function which will
// return a promise, but will also allow you to await.
// async function sign(req, res) {
const sign = async (req, res) => { // This is same as above line
const username = req.body.username.toLowerCase().trim();
// const email = req.body.email.trim();
if (!username) {
console.log('username was empty')
return res.status(401).send({
status: false,
message: "Username cannot be empty"
});
}
if (!req.body.password) {
console.log('password was empty')
return res.status(401).send({
status: false,
message: "Password field cannot be empty"
});
}
return await User.findOne({ where: { username } })
// I'm making this one async also to ensure user.generateAuthToken()
// has a value before it proceeds to res.send()
.then(async (user) => {
if (!user) {
console.log('couldnt find user')
return res.status(401).send({
message: "User is not registered"
})
}
else if (!user.validPassword(req.body.password)){
console.log('password was incorrect')
return res.status(401).send({
message: "The password is incorrect"
})
}
const token = await user.generateAuthToken();
// I added a return here
return res.header('x-auth', token).status(200).send({
statusCode: 200,
message: `Welcome back, ${user.username}`,
user
});
})
.catch((error) => {
console.log('lets put data in here: ' + error)
return res.status(400).send(error)
})
},
I notice the MongoDB search User.findOne({ where: { username } }). I can't remember if it needs to be $where. I think MongoDB syntax uses a $. That could be your problem, and it will fire that console.log('couldnt find user') if so. That might be only for native MongoDB driver. I just Googled and found that the syntax could also be: User.findOne({ username }) which is shorthand for User.findOne({ username: username }).
Some people will tell you it is redundant to do return await fn() and omit the await, but it will throw an unhandled promise rejection if the promise is rejected. It will be caught if await is there. This is part of the upper scope's error handling architecture.
I recommend watching some async/await tutorials, because I see you mixing in a little bit of callback sauce. Your code is pretty good, but I think you can take it to the next level. It looks like you're ready.
Fun fact, you can also omit { and } if your if statement only has one expression, ie:
if (err) {
throw err;
}
can be shorthand:
if (err) throw err;
This can go a long way to help clean up the code, but async/await syntax with try/catch blocks used properly along with throw will do incredible improvements to synchronous looking code with minimal nesting.
Here is how you could re-write some of this, because I want to show you how we can get rid of nesting that adds confusion to your flow control:
const sign = async (req, res) => {
try {
const username = req.body.username.toLowerCase().trim()
if (!username) throw 'noUsername'
if (!req.body.password) throw 'noPassword'
const foundUser = await User.findOne({ username })
if (!foundUser.username) throw 'notFound'
// I assume this returns Boolean
const validPassword = await user.validPassword(req.body.password)
if (!validPassword) throw 'invalidPassword'
// Alter generateAuthToken() to throw 'badToken' if it fails
const token = await user.generateAuthToken()
return res.header('x-auth', token).status(200).send({
statusCode: 200,
message: `Welcome back, ${user.username}`,
user
})
} catch (error) {
// errors are manually thrown into here, and rejected promises
// are automatically thrown into here
if (error === 'noUsername') return res.status(401).send({
status: false,
message: 'Username cannot be empty'
})
if (error === 'noPassword') return res.status(401).send({
status: false,
message: 'Password field cannot be empty'
})
if (error === 'notFound') return res.status(401).send({
message: 'User is not registered'
})
if (error === 'invalidPassword') return res.status(401).send({
message: 'The password is incorrect'
})
if (error === 'badToken') return res.status(403).send({
message: 'User is not authorized'
})
return res.status(400).send(error)
}
}
sign(req, res).then((response) => console.log(response))
Hopefully, this has been helpful :) and sorry I don't use semi-colons.
Related
I have a node server, this is the login route. I am trying to send a response after checking if the user exists in the DB and if the password match the hash value. I am using a custom flag but the code where I check for the value is executed before the value can be changed.
router.route('/login').post( (req,res) => {
User.findOne({email: req.body.email} ,(err,user) => {
let check = false
if(!user){
return res.json({
loginSuccess:false,
message : "Invalid Email or Password"
})
}
user.comparePassword(req.body.password,(err, isMatch)=> {
if(err){
return res.json({
loginSuccess : false,
message : "Can't Login. Try Again!"
})
};
if(isMatch){
check = true
}
})
if(!check){
return res.json({
loginSuccess : false,
message : "Invalid Eail or Password"
})
}
user.generateToken((err, user) => {
if(err) {
return res.status(400).send(err)
}
else{
res.cookie('x_auth' , user.token)
.status(200)
.json({
user : user,
loginSuccess:true
})
}
})
})
})
In the code above, how can I make
if(!check){
return res.json({
loginSuccess : false,
message : "Invalid Eail or Password"
})
}
Wait for this:
user.comparePassword(req.body.password,(err, isMatch)=> {
if(err){
return res.json({
loginSuccess : false,
message : "Can't Login. Try Again!"
})
};
if(isMatch){
check = true
}
})
Actually the code below executes first before the flag is changed, but I want it to wait, so it can decide on the new value of check
You want to do your checking inside the callback.
router.route('/login').post((req, res) => {
User.findOne({ email: req.body.email }, (err, user) => {
if (!user) {
return res.json({
loginSuccess: false,
message: "Invalid Email or Password"
})
}
user.comparePassword(req.body.password, (err, isMatch) => {
if (err) {
return res.json({
loginSuccess: false,
message: "Can't Login. Try Again!"
})
};
if (!isMatch) {
return res.json({
loginSuccess: false,
message: "Invalid Eail or Password"
})
}
user.generateToken((err, user) => {
if (err) {
return res.status(400).send(err)
}
else {
res.cookie('x_auth', user.token)
.status(200)
.json({
user: user,
loginSuccess: true
})
}
})
})
})
})
You need to convert that callback into promise:
await new Promise((resolve, reject) => {
user.comparePassword(req.body.password, (err, isMatch)=> {
if(err){
return res.json({
loginSuccess : false,
message : "Can't Login. Try Again!"
});
};
if(isMatch){
check = true;
resolve();
}
});
});
You can use Javascript's Promise API, where the executor function would execute your login and the .then and .catch would be executed after the promise's executor, depending on whether it was fulfilled. The documentation provides this example:
let myFirstPromise = new Promise((resolve, reject) => {
// We call resolve(...) when what we were doing asynchronously was successful, and reject(...) when it failed.
// In this example, we use setTimeout(...) to simulate async code.
// In reality, you will probably be using something like XHR or an HTML5 API.
setTimeout( function() {
resolve("Success!") // Yay! Everything went well!
}, 250)
})
myFirstPromise.then((successMessage) => {
// successMessage is whatever we passed in the resolve(...) function above.
// It doesn't have to be a string, but if it is only a succeed message, it probably will be.
console.log("Yay! " + successMessage)
});
Basically you will need to resolve/reject the promise at the end of your login method. You can also resolve this with callbacks, as Duc Nguyen has already suggested, but in that case beware callback hell.
I'm scratching my head here, I'm using bcrypt to try and log in a user but I'm getting the following error:
Error: Can't set headers after they are sent.
Here's the code for that route:
router.post('/login', (req, res, next) => {
User.find({ email: req.body.email })
.exec()
.then(user => {
if (user.length < 1) {
res.status(401).json({
message: 'Auth failed'
});
}
bcrypt.compare(req.body.password, user[0].password, (err, result) => {
if (err) {
res.status(401).json({
message: 'Auth failed'
});
}
if (result) {
res.json(200).json({
message: 'Auth successful'
});
}
return res.status(401).json({
message: 'Auth failed 3'
});
});
})
.catch(err => {
res.status(500).json({
error: err
});
});
});
I thought the error might've come from the if else statements and trying to send a header twice, but I'm ending them before moving on the next conditional no? Am I miss-reading something here?
The error you see is caused when your code tries to send more than one response for the same request. The typical cause for this error is a coding mistake in how you handle asynchronous operations. In your code you show, I can see the following mistakes that can cause this error:
If user.length < 1, then you do res.status(401).json(...), but then you let the code continue to run where you then send other responses.
If you get an error from bcrypt.compare(), you send an error response and then let the code continue to run and send other responses.
If bcrypt.compare() succeeds, you send res.json(200).json(...) which is just wrong. You probably meant res.status(200).json(...).
If bcrypt.compare() succeeds and you have a result, you send two responses.
In looking at your code, it appears that you think that as soon as you do res.json() that the function returns and no other code executes. That is not the case. Until you hit a return, the rest of the code in that function continues to execute.
Here's one way to fix it (adding a few return statements and one else):
router.post('/login', (req, res, next) => {
User.find({ email: req.body.email }).exec().then(user => {
if (user.length < 1) {
res.status(401).json({message: 'Auth failed'});
return;
}
bcrypt.compare(req.body.password, user[0].password, (err, result) => {
if (err) {
res.status(401).json({message: 'Auth failed'});
return;
}
if (result) {
res.json({message: 'Auth successful'});
} else {
res.status(401).json({message: 'Auth failed 3'});
}
});
}).catch(err => {
res.status(500).json({error: err});
});
});
Or, to do this in a little cleaner fashion where all responses with the same status are combined and all flow control is done with promises, you can do something like this:
const util = require('util');
bcyrpt.compareAsync = util.promisify(bcrypt.compare);
router.post('/login', (req, res, next) => {
User.find({ email: req.body.email }).exec().then(user => {
if (user.length < 1) {
throw new Error('No user match');
}
return bcrypt.compareAsync(req.body.password, user[0].password).then(result =>
if (!result) {
throw new Error('Auth failed 2');
}
res.json({message: 'Auth successful'});
}
}).catch(err => {
res.json(401).json({message: err.message});
});
}).catch(err => {
res.status(500).json({error: err});
});
});
I'm trying to chain a second then method after the first one but it's not working correctly for some reason. It only works fine when I'm nesting the then method. Here's the code that doesn't work correctly:
auth.post('/signup', (req, res, next) => {
const { username } = req.body
const { password } = req.body
Users.findOne({ username })
.then(
existingUser => {
if (existingUser) return res.status(422).send({ error: 'Username is in use' })
const user = new Users({ username, password })
user.save()
},
err => next(err)
)
.then(
savedUser => res.send({
username: savedUser.username,
password: savedUser.password
}),
err => next(err)
)
})
Here when I post to '/signup' user gets saved into the database but I don't get the response with username and password. However:
auth.post('/signup', (req, res, next) => {
const { username } = req.body
const { password } = req.body
Users.findOne({ username })
.then(
existingUser => {
if (existingUser) return res.status(422).send({ error: 'Username is in use' })
const user = new Users({ username, password })
user.save()
.then(
savedUser => res.json({
username: savedUser.username,
password: savedUser.password
}),
err => next(err)
)
},
err => next(err)
)
})
This works as expected. user gets saved and I get the response with username and password. I've read that you can chain these then methods in a flat way without nesting. But I've checked questions on here and couldn't find an answer as to what I'm doing wrong here. Can someone please help with this issue?
Simple 3 step process:
Return a promise from the first .then call.
Change this:
// ...
const user = new Users({ username, password })
user.save()
// ...
to this:
// ...
const user = new Users({ username, password })
return user.save()
// ...
(Mind the return keyword, which will chain it with the second .then() call)
2. Reject the Promise in case existingUser returns false (thanks #JaromandaX for pointing out)
Change this:
if (existingUser) return res.status(422).send({ error: 'Username is in use' })
to this:
if (existingUser) {
res.status(422).send({ error: 'Username is in use' });
return Promise.reject('USER_EXISTS');
}
3. Drop the .then(onResolvedFunction, onRejectedFunction) pattern when possible, and use .catch(err) instead (to catch for a bigger spectrum of errors).
Delete the second argument from your .then()'s
,
err => next(err)
use a .catch instead:
Users.findOne({ username })
.then(...)
.then(...)
.catch((e) => { // <-- Handle the error properly
console.log(e);
if (e !== 'USER_EXISTS')
next(err);
});
Mongoose Footnote!
This has nothing to do with promises. I see you named your model Users, but remember that, internally, Mongoose will pluralize your model names for you. You should either:
Name your model User; or
Explicitly set the pluralized form in a third argument, like this:
const Users = mongoose.model('User', UserSchema, 'Users');
You have at least three issues with your "chained" version
You are not returning anything from your first .then
in the case of existing user, the chained .then would still be executed
in the case of an rejection in Users.findOne the chained .then would also be executed
To fix:
simply return .save()
return a Promise.reject - alternatively you can throw an error
don't use onRejected functions in .then, just have a single rejection handler, at the end of the chain, in a .catch
I would chain that code like this:
auth.post('/signup', (req, res, next) => {
const { username } = req.body
const { password } = req.body
Users.findOne({ username })
.then(existingUser => {
if (existingUser) {
return Promise.reject({
status:422,
error: 'Username is in use'
});
}
return new Users({ username, password }).save();
})
.then(savedUser => res.send({
username: savedUser.username,
password: savedUser.password
}))
.catch(err => {
if (err.status) {
return res.status(err.status).send({ error: err.error });
}
return next(err);
});
});
I'm refactoring our code into promises.
Two blocks with sample code:
user.service.js
export function updateUserProfileByUsername(req, res) {
userController.getUserByUsername(req.params.username)
.then((userProfile) => {
return userController.saveUserProfileByUser(userProfile,
req.body.email,
req.body.username,
req.body.firstname,
req.body.lastname)
})
.then((updated_user) => {
res.status(200).json(updated_user.profile);
})
.catch((err) => {
res.status(404).send('something went wrong');
});
}
export function getUserProfileByUsername(req, res) {
userController.getUserProfileByUsername(req.params.username)
.then((userProfile) => {
res.status(200).json(userProfile);
})
.catch((err) => {
res.status(404).send('something went wrong');
})
}
user.controller.js
export function getUserProfileByUsername(username) {
return User.findOne({
'username': username
}).exec()
.then((user) => {
if (user)
return user.profile;
else
throw new Error("user not found!");
});
}
export function getUserByUsername(username) {
return User.findOne({
'username': username
}).exec()
.then((user) => {
if (user)
return user;
else
throw new Error("user not found!");
});
}
export function saveUserProfileByUser(user, email, username, firstname, lastname) {
user.email = email;
user.username = username;
user.firstname = firstname;
user.lastname = lastname;
return user.save(); // returns a promise
}
Our routes enter in user/index.js, go into service.js and the controller handles our database work and errors.
What I am trying to achieve is to send fitting errors to the client.
Like: 'the user does not exist' or 'username is too long' when updating a wrong user, etc.
if I try to send the error to the client, i'll just get a empty json as result ({}). If I log the error, i get the full stack trace, including validation errors.
.catch((err) => {
console.log(err) // shows me full stacktrace of the error
res.status(404).send(err); //sends {} to the client
})
How can I implement this with promises? Should i add extra middleware sending correct error messages?
I would really appreciate some hints in the right direction for this one.
Thanks in advance!
Because err is an object I assume express is converting to JSON for you. However, when you stringify an error you get '{}';
If you want to return the stacktrace try .send(err.stack).
Alternatively if you only want the message and not the entire stack you can use err.message.
.catch((err) => {
console.log(err)
res.status(404).send(err.stack);
})
So I'm using Node.js + Mongoose + Lie as a promise library. The code is as follows:
var User = require('../models/user'),
Promise = require('lie'),
jwt = require('jsonwebtoken'),
config = require('../../config');
module.exports = function(express) {
var router = express.Router();
router.post('/', function(req, res) {
if (!req.body.username) return res.json({ success: false, reason: 'Username not supplied.' });
if (!req.body.password) return res.json({ success: false, reason: 'Password not supplied.' });
var findUser = new Promise(function(resolve, reject) {
User.findOne({ username: req.body.username }, function(err, user) {
if (err) reject (err);
if (!user) reject({ success: false, reason: 'User not found or password is incorrect.' });
if (!user.validPassword(req.body.password)) reject({ success: false, reason: 'User not found or password is incorrect.' });
resolve(user);
});
});
var sendToken = function(user) {
var token = jwt.sign({ username: user.username }, config.secret, { expiresIn: 2 * 60 * 60 });
res.json({ success: true, token: token });
};
findUser.then(function(value) {
sendToken(value);
}).catch(function(reason) {
res.send(reason);
});
return router;
};
So basically it's an auth route, which sends a signed jwt if everything is good. The strange behavior is that if I send a wrong username, the server throws an error saying
TypeError: Cannot read property 'validPassword' of null
So, it says that the user === null when it reaches the password validity check, HOWEVER, before this validity check, there is a check that a user is found, mainly
if (!user) reject({ success: false, reason: 'User not found or password is incorrect.' });
And here the server already knows that user === null, hence it should reject this promise with success: false, and this piece of code should be thrown into catch part, but it doesn't happen.
Any ideas, please?
I should add that if I change that if (!user.validPassword... part to else if instead of if, it works like it should. However I cannot understand why any code is still executed after the promise gets rejected.
EDIT
While learning node, I mainly used the MEAN Machine book, which is a great help, and there they use this syntax:
if (!user) {
//
} else if (user) {
//
}
But I guess it should work my way as well.
You are not stopping execution of your function on reject, so if findOne results in empty result validPassword is still get called.
Probably you need to add return:
User.findOne({ username: req.body.username }, function(err, user) {
if (err)
return reject (err);
if (!user)
return reject({ success: false, reason: 'User not found or password is incorrect.' });
if (!user.validPassword(req.body.password))
return reject({ success: false, reason: 'User not found or password is incorrect.' });
resolve(user);
});
Or use else if:
User.findOne({ username: req.body.username }, function(err, user) {
if (err) {
reject (err);
} else if (!user) {
reject({ success: false, reason: 'User not found or password is incorrect.' });
} else if (!user.validPassword(req.body.password))
reject({ success: false, reason: 'User not found or password is incorrect.' });
} else {
resolve(user);
}
});