node.js mongoose session issues. Undefined when defined? - node.js

im developing a small application with login and having an issue accessing session data.
nb: using express 3.0, coffee-script and mongoose
So in my app I have the following:
app.locals.use (req,res) ->
res.locals.session = req.session
res.locals.title = config.title
res.locals.href = config.href
In my login.coffee I do the following:
ModelUser.findOne {email: email, password: password}, (err, results) ->
if err then console.log err
if results?
req.session.user = results
req.session.authenticated = true
res.redirect '/'
else
req.session.error = 'bad login'
res.redirect '/'
If I go to output #{session.user} in Jade, I get [bject,Object], but if I enter in "name" or "email" it returns blank. If I go into console and log the session object in the app.coffee, you can see the following:
if req.session? then console.log req.session.user
returns:
{ _id: '4f9b60ba7a7ab7e7f711920e',
email: 'foobar#gmail.com',
name: 'Demo User',
password: '078d2bf82ee269cd9577588d212655c3a2162dc5b09e7c29c6a343926ee881a9' }
But if do console.log req.session.user.email it gives me a 500 error saying its not defined.. but I dont know why because you can see it is defined.
EDIT:
Solved; It was an async issue with the application continuing on before it got the user.
Had to send a callback to the find and use next()
if req.session.authenticated
require('./lib/getUser')(req.session.uid, (results) ->
res.locals.user = results
console.log res.locals
done()
)
else
done()

There is an async issue with the application flow. The server is continuing before the results are returned, thus being undefined.

Related

postman (thunder client) can send request but axios cannot

I am trying to send http requests using axios to my node backend. For some reason, axios keeps returning a 500 (Internal Server Error) even when thunder client (dollar store version of postman) is able to send the request and get a proper response.
index.js (server)
app.get('/api/login', async (req, res) => {
try {
const user = await User.findOne({ email: req.body.email })
if(user===undefined) { res.status(404).json("user not found"); }
const validPassword = await bcrypt.compare(req.body.password, user.password)
!validPassword && res.status(400).json("wrong password")
res.status(200).json(user)
} catch (err) {
res.status(500).json(err)
}
})
Login.js (frontend)
const login = (email, password) => {
console.log(email + ': ' + password)
axios.get('http://localhost:8800/api/login', { email: email, password: password })
.then((response) => console.log(response))
.catch((err) => console.log(err.response))
}
err.response returns no useful data and err.response.data is a blank object. I've tried to edit the request header, but it is already 'application/json'. Again, this request works on thunder client and I made sure that the data I passed in was correct through the console.log(email + ': ' + password . I've been trying to fix this issue for hours so please help. Thank you in advance.
Update: I had previously binded the login function to an onClick to a button, but I put the axios function directly into the brackets instead of login(email, password). The issue persists.
Second Update: I followed the comments' advice and console logged the error on the terminal. It returned TypeError: Cannot read properties of null (reading 'password'). This was strange because in the function, I had console logged password and it returned the proper text. It also says that it cannot find a user within my database that uses the email I am currently using, but even when I pass in the exact email I use in thunder client requests, I still get the error. I think the data is not getting there properly.
Third Update: My hypothesis is confirmed. In index.js, I made the route console log req.body.email and it returned undefined. I passed in an object that I JSON stringified and when console logged in the browser, it returns a proper object. The request is sending an object with undefined properties although I am passing in an object with values
In this case, the issue was that the request was a get request, not a post request. Get requests do not take in data while post requests do. Here is the fix:
index.js (server)
app.post('/api/login', async (req, res) => {
try {
const user = await User.findOne({ email: req.body.email })
if(user===undefined) { res.status(404).json("user not found"); }
const validPassword = await bcrypt.compare(req.body.password, user.password)
!validPassword && res.status(400).json("wrong password")
res.status(200).json(user)
} catch (err) {
res.status(500).json(err)
}
})
If you have to receive the request parameters in body (mainly in json format) then you have to go with POST type request.
In the GET type request, you can get request parameters in the form of params and query string.
Parameters may be either part of path:
myapi/customers/123
or a query string:
myapi?customer=123
More details can be found here:
https://www.restapitutorial.com/lessons/httpmethods.html

Passport fails authentication with "Wrong password" before actually calling my passwordMatch function

This is a weird one.
What im trying to do
Create an authentication server in node.js using Passportjs local strategy, and JWT. Allowing for account registration with email & password, with passwords hashed with 'crypto'
What's happening
So when I login with the right password, to a pre existing model, authentication fails in the APi for having a wrong password. Though theres some weird stuff going on.
What I've tried
Essentially when I make the post request:
OPTIONS /api/login calls
It goes through my passport config, and in the typical function where you check if the password is correct
side note: POST api/login is logged to the console
The function in my passport config:
if (!profileController.passMatch(username, password)) {
console.log('pass was wrong');
return done(null, false, {
message: 'Password is wrong'
});
}
The 'pass was wrong' thing calls, failing the authentication with done(). Though in passMatch, as you'll see below, it does show the correct password
passMatch function in profile controller:
module.exports.passMatch = (email, password) => {
User.findOne({email: email}, (err, user) => {
if (err) { console.log ("error at passMatch: " + err); }
var hash = crypto.pbkdf2Sync(password, user.salt, 1000, 64, 'sha512').toString('hex');
console.log(user.hash == hash);
return (user.hash == hash);
});
return false;
};
Though if you notice the console log where I check if the hash comparison is correct. That log statement is printed to the console after 'pass was wrong' is logged. It's also printed after the passport.authenticate call in my login function concludes a failed authentication at the console.log(info)
Login function:
module.exports.login = (req, res) => {
console.log('beginning to authenticate');
passport.authenticate('local', (err, user, info) => {
console.log ("authenticating");
var token;
// If passport throws an error
if (err) {
res.status(404).json(err);
console.log("error logging in");
return;
}
// If a user is found
if (user) {
// Respond with JWT
token = createJwt(user)
res.status(200);
res.json({
"token": token
})
console.log("user logged in");
// If a user wasn't found
} else {
res.status(401).json(info);
console.log(info);
}
})(req, res);
};
Error logging in isn't called, but the console.log(info) is called with the error message from the done() message in the config.
What's going wrong here?
In the "passMatch" function, I query for the user again (which is just inefficient), but since this operation was asynch, it was being skipped to the "return false" statement after, and in the passport authentication config process, it recieved that false, causing authentication to fail, but the "log" to be returned after cause it took longer.
How I fixed it
I passed in the user object that passport already queried instead of the username into passMatch, then had two operations to check if the hash was the same and returned that, and now it works.
The new code
module.exports.passMatch = (user, password) => {
var hash = crypto.pbkdf2Sync(password, user.salt, 1000, 64, 'sha512').toString('hex');
return user.hash == hash;
};
Also the necessary change in the passport config to pass in the user instead of the username as the first param to that function.

express-session: Why is redirect executed before my session info is set?

Yet another "I've just started learning node.js/express and now I stuck" person here. I've tried looking everywhere for answers but I've reached a dead end. This is my first Stackoverflow question, so please tell me if I have done something wrong or unconventional.
I'm trying to create a POST request that saves a User-object to an express-session (on MongoDB), and redirects you to a URL that handles your session information.
The problem is, that the user is redirected before the header is set, and I am given the following:
Error: Can't set headers after they are sent.
Here is my code. I know it's a lot.. Im sorry.
router.post()
// Handler for POST requests from root page (/)
router.post('/', function(req, res) {
console.log("Router: A POST request for: \"/\" has been called!");
var username = req.body.username;
var password = req.body.password;
// Connect to database
mongoose.connect(dbAddress);
var db = mongoose.connection;
// Report if error in connection
db.on('error', console.error.bind(console, 'Database: Connection error..'));
// Callback function on 'open'
db.once('open', function() {
console.log("Database: Connection successful!");
console.log("Database: User supplied username: " + username);
console.log("Database: User supplied password: " + password);
// Find User object with a userId of req's username
User.findOne({ 'userId' : username.toUpperCase() }, function(err, userObj) {
if (err)
return console.err(err);
// Disconnect when done retrieving user object
mongoose.disconnect();
if ( userObj ) {
console.log("Database: Returned password from MongoDB:");
console.log(userObj.password);
var db_password = userObj.password;
if (password === db_password) {
console.log("Database: User Authenticated");
// Set 'req.session.user' as cookie
req.session.user = userObj;
// Redirect to homepage, with new cookie set
res.redirect('/');
} else { // If passwords don't match
res.render('index', {
showError: true
});
}
} else { // If userObj is null
res.render('index', {
showError: true
});
}
});
});
});
Note the 'req.session.user = userObj' part. Here I am trying to set 'user' in the session to the user object retrieved from MongoDB. In the next line, I am redirecting the user back to the GET request handler for '/' which handles the user based on the session information.
For some reason, these aren't happening in order. The GET request handler doesn't find req.session.user.
I understand that node.js is all about asynchronisation, but from other examples I've found online, this is supposed to work. What am I missing here?
You could put your redirect inside a callback after the session is saved e.g:
...
// Set 'req.session.user' as cookie
req.session.user = userObj;
req.session.save(function(err) {
// session saved
res.redirect('/')
})
...
Hopefully this will make sure that the user is only redirected after the session is saved.
Note: Make sure you hash your password with something like Bcrypt or pbkdf2.

Check for existing user using Mongoose

I'm trying to write a middleware function that (when a POST request is made with a username/password) checks to see if the user being created already exists in the database. I don't know if I'm doing this properly though.
User.find({ username: req.body.username }) returns an object which contains (or does not contain) the user if it exists...but how to properly return to exit if a user under the same username is found? Whenever I test this with Mocha, res.body.msg comes up as undefined.
Code:
module.exports = exports = function(req, res, next) {
User.find({ username: req.body.username }, (err, user) => {
if (err) return handleDBError(err, res);
if (user) return res.status(200).json({ msg: 'an account with this username already exists' });
});
next();
};
User Schema:
var userSchema = new mongoose.Schema({
username: String,
authentication: {
email: String,
password: String
}
});
give it a try very initial create a function to get the user response
function findUser(arg, callback) {
/* write your query here */
return callback(pass response here)
}
And then use it where you want
findUser(arg,function(callbackResponse) { /*do something*/ })
Since nodejs is asynchronous, chances are that the response is being sent after you are trying to read it. Make sure to keep the reading process waiting untill response is sent. I personaly use passport for handling that.

sails session writing bug

I'm using sails 0.10.4 and stumbled with one pretty annoying bug. When user logs in I write his data into the req.session.user then in policies I can retrieve his data such as his role, password etc. But the req.session.user becomes undefined when I go out of the login action. Do you have any ideas how to handle this? Here's the code:
api/controllers/User.js :
module.exports = {
login: function (req, res) {
Users.findOneByEmail(req.param('email'))
.exec(function (err, user) {
if ((err) || (!user)) {
res.send({
error: 'User not found'
});
return;
}
if (!passwordHash.verify(req.param('password'), user.password)) {
res.send({
error: 'Incorrect passwpord'
});
return;
}
req.session.user = user;//I write user into the session
res.send({
user: user
});
});
}
}
api/policies/isLoggedIn.js
module.exports = function (req, res, next) {
if (req.headers.authentication) {
var credentials = JSON.parse(req.headers.authentication);
if(req.session.user.login === credentials.login)//User doesn't exist in session
return next();
}
}
In a testing environment , this issue can happen when testing with Supertest and not defining an agent
var agent = request.agent(app);
agent.post('/api/login',{email:'foo#bar.com',password:'foobar})
.end(function(err,res){...; done();});
It is the correct way to work with sessions, simply using request.post would not work as it would reinit the session variable as soon as the response is sent, even if we are chaining requests inside the same test.
Learnt it the hard way, so I hope it can help some lost developper.

Resources