Return value from one function to another with Node.JS - node.js

I am working on a login interface using the MEAN stack. I have managed to get it to work using PassportJS. My problem now is I need a way to let my client-side know whether the person logging in is an admin or user(user role). These info are available from my MongoDB.
The flow of my API call is as follow :
app.post('/login', passport.authenticate('local'), authRoutes.loginCheck);
First, it runs the passport.authenticate where it calls the function below
function verifyCredentials(username, password, done) // username & password from what user provide when logging in
{
console.log('VC');
User.findOne({username: username}, function(err, user) //query Mongo
{
console.log(user); // User role is available here, in JSON format
if(user === null) // if no username in database, do this
{
console.log('Username does not exist in database');
}
else
{
user.comparePassword(password, function(err, match) // function written to compare hashed password in Mongo & password provided by user
{
if(match)
{
done(null, {id: username, name: username});
return user; // this is not the correct syntax, but the idea is, I want to send over the user details here, so I can access the role later
}
else
{
done(null, null);
}
});
}
});
}
The verifyFunction is called with this syntax.
passport.use(new LocalStrategy(verifyCredentials));
Once that function is successfully called, the server executes the 2nd part of it which is the loginCheck.
module.exports.loginCheck = function(req, res)
{
console.log('Calling loginCheck route');
// I generate some sort of jwt token here
// payload, body, blah blah blah ...
console.log(req.body);
res.json({
authenticated: req.isAuthenticated(), //built-in authentication function, returns true or false
token: token // sends over token
role: user.role // want to send over something like this
}); // sends all these to client side as JSON
}
Since both functions are in different files, I am unclear if I have to require something or simply just pass an extra parameter to the loginCheck function. I have tried the latter though and it did not work.
One way that I could think of is do another Mongo query in the loginCheck function, but that would be kinda redundant.
Even a specific keyword for me to google up would definitely be of big help as don't I know what I should be looking for. The reason is because I am new to NodeJS, thus I am not familiarize with most of the terms yet.
I think these codes should suffice but if I am needed to provide more, let me know and I will do so. Thanks in advance !!

To pass control to next matching route you need to use next that passes as third argument in the routes:
function verifyCredentials(req, res, next) {
User.findOne({username: req.body.username}, function(err, user) //query Mongo
{
if(user === null) {
return next(new Error('Username does not exist in database'));
} else {
user.comparePassword(req.body.password, function(err, match) {
if(match) {
next(null, {id: username, name: username});
} else {
next(new Error('not match'));
}
});
}
});
}
app.post('/login', verifyCredentials, authRoutes.loginCheck);

Related

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.

Why is passport.authenticate needed in a registration?

I'm learning Passport, using local strategy and Mongoose, through passport-local-mongoose plugin.
It makes sense to me in a /login for example, but I don't understand why passport.authenticate is needed in a registration, right after it having already taken place...
Can anyone explain me, please? Also, what if it fails?
The following code was taken from a tutorial, but I have found similar constructions all over.
router.post('/register', function(req, res) {
User.register(new User({ username : req.body.username }),
req.body.password, function(err, user) {
if (err) {
return res.status(500).json({err: err});
}
if (req.body.firstname) {
user.firstname = req.body.firstname;
}
if (req.body.lastname) {
user.lastname = req.body.lastname;
}
user.save(function(err,user) {
passport.authenticate('local')(req, res, function () {
return res.status(200).json({status: 'Registration Successful!'});
});
});
});
});
The password isn't used to authenticated the user inside this snipped.
Call User.register
Creates a new User Object with the username, and needs the password to do
some magic with it. Probably build a hash which gets added to your 'user' object.
Call user.save
Saves this 'user' object inside your Storage, if this is successful the user gets authenticated, and returns HTTP 200 which ends the registration process.
Error Handling isn't implemented in this save method, for that you will have an exception on error.
Hope this helps to understand, your snippet.
To use your /login method your new user can use his username and password which he gave to you during the registration process.

Sails.js : How to return access token after user registration using sails-generate-auth?

I am using sails-generate-auth in my sails.js app. I followed this tutorial to integrate this with my app. When I call localhost:1337/auth/local/register it routes to my callback action in AuthController. My callback action is as follows
callback: function (req, res) {
function tryAgain(err) {
//some validation
}
}
passport.callback(req, res, function (err, user, challenges, statuses) {
if (err || !user) {
return tryAgain(challenges);
}
req.login(user, function (err) {
if (err) {
return tryAgain(err);
}
//Return the access token created by passport instead of success.
res.send("Success");
});
});
I want to replace res.send("Success"); with the access token created by passport. But User.passport seems to be null at this point. How do I get the user's access token at this point?
The User You Get only contains the data from the 'User Collection' which contains the username email and id. The 'Passport' collection is a seperate collection which contains hashed password, ID, userID(which is equal to the ID In the 'User' Collection) and a token. You need to search in the 'passport' collection for the relavent User. Here is the algoritem:
passport.callback(req, res, function (err, user, challenges, statuses) {
if (err || !user) {
console.log(err);
return tryAgain(challenges);
}
req.login(user, function (err) {
if (err) {
console.log(err);
return tryAgain(err);
}
// Mark the session as authenticated to work with default Sails sessionAuth.js policy
req.session.authenticated = true
console.log(user);
var userID = user.id;
Passport.find({user: userID}, function(err, items){
if(err) return err;
console.log(items[0].accessToken);
// Make sure you dont give them any sensetive data
res.json({userData: user, token: items[0].accessToken});
});
// Upon successful login, send the user to the homepage were req.user
//res.redirect('/');
});
});
If you want to use sails built-in sessions, you do not need to send any token to the client, everything is stored server-side in the sessions and it is tied to the user by the sid (session id) cookie, therefore you can redirect to any page.
As long as you have your sessionAuth policy it will check that the user is logged-in before accessing your protected routes.
If you would like to use something like Json Web Tokens (JWT) though, sails-generate-auth / sails-auth do not support it yet

Access to "req" Object in Supertest After a Response

Is there any way to directly access the req object in supertest, while/after the request is being tested? I want to test my passport strategies, so I want to check req.user, req.session, and perhaps others. I know I can test page redirects or flash, as those are what my strategies do, but it seems useful to see if there is a user on the req object, as well. If I do this, I can also check how many users there are at any one time.
I will sign users up with the "local-signup" strategy, which is defined thusly:
'use strict';
// get passport & mongoose
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var UserModel = require('mongoose').model('User');
module.exports = function() {
// signup function
passport.use('local-signup', new LocalStrategy({
passReqToCallback: true // pass the entire request to the callback
},
function(req, username, password, done) {
process.nextTick(function() {
// find a user with the same username
UserModel.findOne({username: username}, function(err, user) {
// if there is an error, log it then return it
if(err) {
console.log("Error finding a user in the database: " + err);
return done(err);
}
// if a user was already found
if(user) {
return done(null, false, "User already exists");
}
// if we get this far, create a new user from the request body
var newUser = new UserModel(req.body);
// save it and sign it in
newUser.save(function(err) {
if(err) {
console.log("Error during signup: " + err);
return done(err);
}
return done(null, newUser);
});
});
});
}
));
};
One way I use this strategy is like this:
My "local" strategy is defined like this:
'use strict';
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var UserModel = require('mongoose').model('User');
module.exports = function() {
// create our local passport strategy & use it
passport.use(new LocalStrategy({
// use the default names for the username & password fields
usernameField: 'username',
passwordField: 'password'
},
// main strategy function
function(username, password, done) {
// find user with given username
UserModel.findOne({
username: username
},
// with this username, do this
function(err, user) {
// if there's an error, log it then pass it along
if(err) {
console.log("Error during login: " + err);
return done(err);
}
// if the username and/or password is incorrect, return an error
// along with a message
if(!user || !user.authenticate(password)) {
return done(null, false, {
message: 'Invalid username and/or password'
});
}
// if everything is correct, return the user document from the database
return done(null, user);
});
}
));
};
I use both strategies like this, for example:
app.route(pageName).post(function(req, res, next) {
passport.authenticate(strategyName, function(err, user, info) {
if(err || !user) {
res.status(401).send(info);
}
else {
req.login(user, function(err) {
if(err) {
res.status(400).send(err);
}
else {
res.send(null);
}
});
}
})(req, res, next);
});
I tried
request = require('supertest');
this.authServer = require('../my-server');
request(this.authServer)
.put('/signup')
.set('Content-Type', 'application/json')
.set('Host', 'konneka.org')
.send(this.fullUser)
.end(function(req, res, done) {
console.log(res);
});
The res object I logged, inside the end() function, which was way too long to show here, has a req object defined on it, but it seems to only have the objects & functions that were defined before the request was opened. In other words, it does not have req.user, req.session, or other objects I want, because they are defined after the request completes and a new request is started. I noticed it has status codes, as well, which are only defined after the request completes, so I must be missing something.
Is there any way to get access to the req object after the request you are testing is ended? Or am I going about this completely the wrong way?
You cannot do what you want using supertest.
Not sure if this helps but I'll add a little context to clarify the answer:
supertest is a wrapper on top of superagent (client side) with some basic hooks into express to start up the HTTP listener. Under the hood, it really is not any different from starting up your express app, waiting for it to listen on a port, making an HTTP request to that port, and parsing the result. In fact, that is exactly what it does.
So essentially supertest only has access to what ever your client would have access to (a browser or some API client). In other words, if it isnt in the HTTP response body, you wont have access to it. req.user and req.sesssion are server side state variables that are (most likely) not in the response (unless you are doing something strange).
If you want to test in exactly the way you describe, you will have to use some alternative strategy of testing, not supertest.
I found this question when I thought I wanted to do this, and for me it worked well to check the status of the user created by the request instead of verifying the content of the req object. You do have access to the full database where I assume you users somehow ends up.

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