Nodejs + Passport: How to add more user information - node.js

I'm new to Nodejs and looking for a user credential management, I see that Passport a good option.
But in Passport's registration strategy, I only see that user information saved is email and password.
I need more user information like full name, job, registration time, last activity time, etc.
So how can I do this in Passport?

Inside of your Passport.js signup/register strategy you should be able to just pass the request object in as the first parameter of that function and Passport will take care of passing your request into your function for you.
So you should be able to use the req.body object, get the data from your form that way and store that to your database.
Below is a more detailed example of how this would work.
passport.use('signup', new LocalStrategy({
passReqToCallback : true
},
function(req, username, password, done) {
findOrCreateUser = function(){
// find a user in Mongo with provided username
User.findOne({'username':username},function(err, user) {
// In case of any error return
if (err){
console.log('Error in Signup: ' + err);
return done(err);
}
// already exists
if (user) {
console.log('User already exists');
return done(null, false,
req.flash('message','User Already Exists'));
} else {
// if there is no user with that email
// create the user
var newUser = new User();
// set the user's local credentials
newUser.username = username;
newUser.password = createHash(password);
newUser.firstName = req.body.firstName;
newUser.lastName = req.body.lastName;
// save the user
newUser.save(function(err) {
if (err){
console.log('Error in Saving user: '+err);
throw err;
}
console.log('User Registration succesful');
return done(null, newUser);
});
}
});
};
// Delay the execution of findOrCreateUser and execute
// the method in the next tick of the event loop
process.nextTick(findOrCreateUser);
});
);
Here is a tutorial that covers it in a bit more detail. I did change the firstName and lastName parameters from params to variables in the body. But you can either use params or body to get that data into your local strategy.

when making a request for the newUser args other params should be requested from the form body like so
newUser.local.fname = req.body.fname

I found that this worked better. It seemed to be key that the done be after the req, username, password but before the other variables your desired to pass into the function. If done was placed at the end then:
events.js:160 throw er; // Unhandled 'error' event TypeError: done is not a function
would be returned.
// =========================================================================
// LOCAL SIGNUP ============================================================
// =========================================================================
// we are using named strategies since we have one for login and one for signup
// by default, if there was no name, it would just be called 'local'
passport.use('local-signup', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField : 'username',
passwordField : 'password',
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, username, password, done, fname, lname, email, security_question, security_answer) {
// asynchronous
// User.findOne wont fire unless data is sent back
process.nextTick(function() {
// find a user whose email is the same as the forms email
// we are checking to see if the user trying to login already exists
User.findOne({ 'local.username' : username }, function(err, user) {
// if there are any errors, return the error
if (err)
return done(err);
// check to see if theres already a user with that email
if (user) {
return done(null, false, req.flash('signupMessage', 'That username is already taken.'));
} else {
// if there is no user with that email
// create the user
var newUser = new User();
// set the user's local credentials
newUser.local.fname = fname;
newUser.local.lname = lname;
newUser.local.username = username;
newUser.local.email = email;
newUser.local.password = newUser.generateHash(password);
newUser.local.security_question = security_question;
newUser.local.security_answer = newUser.generateHash(security_answer);
// save the user
newUser.save(function(err) {
if (err)
throw err;
return done(null, newUser);
});
}
});
});
}));

Related

Passport login with 2 user types

I currently have a login up and running using Passport in my Node and Express project. Right now I have a users collection that gets the username and password and logs in fine. However, I have 2 separate user types (nurse and doctor) all which have similar fields with the exception of about 4 fields that are different. I'm not sure how to approach implementing 2 different users with the login. Is it best to just have all under users collection with different fields or should I have a separate collection for both users? and if so how would I implement this?
I'm not sure what code to post that can help other than my login code for passport
var LocalStrategy = require('passport-local').Strategy;
var User = require('../models/user');
var bCrypt = require('bcrypt-nodejs');
module.exports = function(passport){
passport.use('login', new LocalStrategy({
passReqToCallback : true
},
function(req, username, password, done) {
// check in mongo if a user with username exists or not
User.findOne({ 'username' : username },
function(err, user) {
// In case of any error, return using the done method
if (err)
return done(err);
// Username does not exist, log the error and redirect back
if (!user){
console.log('User Not Found with username '+username);
return done(null, false, req.flash('message', 'User Not found.'));
}
// User exists but wrong password, log the error
if (!isValidPassword(user, password)){
console.log('Invalid Password');
return done(null, false, req.flash('message', 'Invalid Password')); // redirect back to login page
}
// User and password both match, return user from done method
// which will be treated like success
return done(null, user);
}
);
})
);
var isValidPassword = function(user, password){
return bCrypt.compareSync(password, user.password);
}
}

nodejs-passport : done is not a function

I am having the "done is not a function" error. I tried to remove the passReqToCallback parameter but it still doesn't work. I am not sure why please help.
It's on the line: return done(null, false, req.flash('signupMessage', 'That username is already taken.'));
// =========================================================================
// LOCAL SIGNUP ============================================================
// =========================================================================
// we are using named strategies since we have one for login and one for signup
// by default, if there was no name, it would just be called 'local'
passport.use('local-signup', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
emailField : 'email',
usernameField : 'username',
passwordField : 'password',
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, email, username, password, done) {
// asynchronous
// User.findOne wont fire unless data is sent back
process.nextTick(function() {
// find a user whose email is the same as the forms email
// we are checking to see if the user trying to login already exists
User.find({ 'local.email' : email, 'local.username' : username }, function(err, user) {
// if there are any errors, return the error
if (err)
return done(err);
// check to see if theres already a user with that email
//if user found.
if (user.length!=0) {
if(user[0].username){
return done(null, false, req.flash('signupMessage', 'That username is already taken.'));
}else{
return done(null, false, req.flash('signupMessage', 'That email is already taken.'));
}
return done(err);
}
else {
// if there is no user with that email
// create the user
var newUser = new User();
// set the user's local credentials
newUser.local.email = email;
newUser.local.username = username;
newUser.local.password = newUser.generateHash(password);
// save the user
newUser.save(function(err) {
if (err)
throw err;
return done(null, true);
});
}
});
});
}));
// =========================================================================
// LOCAL LOGIN =============================================================
// =========================================================================
// we are using named strategies since we have one for login and one for signup
// by default, if there was no name, it would just be called 'local'
passport.use('local-login', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField : 'userIdentityField',
passwordField : 'password',
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, email, password, done) { // callback with email and password from our form
// find a user whose email is the same as the forms email
// we are checking to see if the user trying to login already exists
User.isValidUserPassword(email, password, done);
}));
};

Database error handling using Passport and Mongoose

I've incorporated the authentication code from this tutorial into my application and everything is working. Now I'm going back to make the database error handling more robust. In the code below (from the tutorial), why do they throw the error if they hit a snag with the save()? Is there a reason not to handle more gracefully? Perhaps something like:
if (err)
return done(err, false, req.flash('signupMessage', 'Encountered database error.'));
From the tutorial:
passport.use('local-signup', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField : 'email',
passwordField : 'password',
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, email, password, done) {
// asynchronous
// User.findOne wont fire unless data is sent back
process.nextTick(function() {
// find a user whose email is the same as the forms email
// we are checking to see if the user trying to login already exists
User.findOne({ 'local.email' : email }, function(err, user) {
// if there are any errors, return the error
if (err)
return done(err);
// check to see if theres already a user with that email
if (user) {
return done(null, false, req.flash('signupMessage', 'That email is already taken.'));
} else {
// if there is no user with that email
// create the user
var newUser = new User();
// set the user's local credentials
newUser.local.email = email;
newUser.local.password = newUser.generateHash(password);
// save the user
newUser.save(function(err) {
if (err)
throw err;
return done(null, newUser);
});
}
});
});
}));
solution is simple:
newUser.save(function(err) {
if (err) {
return done(err);
}
return done(null, newUser);
});
even in mongoose documentation saving is done without throwing exception.
solution that You're reading is too old: Dec 04, 2013. Why not to read latest documentation from it's pure source?
Read this: http://passportjs.org/docs/configure
BONUS: I would recommend to shorten Your code by using plugin mongoose findOrCreate

How to generate a unique id and save it to mongoDB in passportJS?

I am using passportJS, in my callback I would like to carry the following operations.
1) generate a random string
2) check the database if that id exists, if it exist then regenrate id untill it is unique.
3) Save the user model (with a unique id).
I tried writing multiple functions but it seems that the newUser object is undefined inside the functions!
Here is my functions I am using from my mongoose model.
userSchema.methods.generateVci = function(length, characters){
var string = '';
for(var i = length; i > 0; --i){
string += characters[Math.round(Math.random() * (characters.length - 1))];
}
return string;
};
userSchema.statics.validateVci = function(uniquekey){
this.find({}, function(err,user){
for(var i = 0; i < user.length; ++i){
var uservci = user[i].local.vci;
if(uservci == uniquekey){
console.log('false');
return false;
}
}
console.log('true');
return true;
});
};
passport.use('local-signup', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField : 'email',
passwordField : 'password',
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, email, password, done) {
// find a user whose email is the same as the forms email
// we are checking to see if the user trying to login already exists
User.findOne({ 'local.email' : email }, function(err, user) {
// if there are any errors, return the error
if (err)
return done(err);
// check to see if theres already a user with that email
if (user) {
return done(null, false, req.flash('signupMessage', 'That email is already taken.'));
} else {
var generatedVciKey = newUser.generateVci(32, '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ');
var isvalidvci = User.validateVci(generatedVciKey);
if(isvalidvci){
var newUser = new User();
newUser.local.email = email;
newUser.local.password = newUser.generateHash(password);
newUser.local.vci = generatedVciKey;
}
// save the user
newUser.save(function(err) {
if (err)
throw err;
return done(null, newUser);
});
}
});
}));
If you could please show me a way to write some sort of recursive function in that passportjs would only save the user model when a unique ( checked with the database) id has been generated. The unique id must be regenerated again and again until it is truly unique from any other ids in the database. I have no clue how to do this as it seems that when I write a function the variables in the passportjs callback becomes undefined.
I rewrote this generateVci but you should really use node-uuid like someone already suggested
userSchema.methods.generateVci = function(length, characters) {
return characters.split('').map(function() {
var randomKey = Math.round(Math.random() * (characters.length - 1));
return characters[randomKey];
}).join('').substring(0, length);
};
Your validateVCI is async so you have to pass a callback or other way is to use promises
userSchema.statics.validateVci = function(uniquekey, cb){
this.find({}, function(err, users){
if (err) {
return cb(err);
}
var isInvalid = users.reduce(function(invalid, user) {
if (invalid) {
return true;
}
return user.local.vci === uniquekey;
}, false);
if (isInvalid) {
return cb(null, false);
}
console.log('true');
return cb(null, true);
});
};
You should make vci field unique in your database ...
So when you try to create a user with same vci it would fail
function createUser(email, password, done) {
var generatedVciKey = newUser.generateVci(32, '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ');
User.validateVci(generatedVciKey, function(isInvalid) {
if (isInvalid) {
return createUser(email, password, done)
}
var newUser = new User();
newUser.local.email = email;
newUser.local.password = newUser.generateHash(password);
newUser.local.vci = generatedVciKey;
// save the user, or try again on error
newUser.save(function(err) {
if (err) {
return createUser(email, password, done);
}
done(null, newUser);
});
});
}
passport.use('local-signup', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField : 'email',
passwordField : 'password',
passReqToCallback : true // allows us to pass back the entire request to the callback
}, function(req, email, password, done) {
// find a user whose email is the same as the forms email
// we are checking to see if the user trying to login already exists
User.findOne({ 'local.email' : email }, function(err, user) {
// if there are any errors, return the error
if (err)
return done(err);
if (user) {
return done(null, false, req.flash('signupMessage', 'That email is already taken.'));
}
createUser(email, password, done);
});
}));
Do you realy need to do this? You can use node-uuid to generate unique identifier. So the probability that you will generate non-uniqe id negligible low

Using sails-generate-auth for local authentication with an existing model?

I would like to use sailsJS with an existing database.
This database already contains a user table (which is named caver) and already has email, username and password (which are named contact, login, password).
I cannot change the schema of this database!
I am currently trying to use sails-generate-auth to setup a local authentication.
Is it possible to link the passport authentication with an existing model (Caver) which is not the automatically created model (User)?
Can I use a custom hash function (the one used for the password in my current database) with the passport service created by sails-generate-auth?
I already succeeded to make my authentication work with the following code but now I would like to use sails-generate-auth instead.
api/services/passport.js (without using sails-generate-auth) :
var passport = require('passport'),
LocalStrategy = require('passport-local').Strategy;
// helper functions
function findById(id, fn) {
Caver.findOne(id).done( function(err, user){
if (err){
return fn(null, null);
}else{
return fn(null, user);
}
});
}
function findByEmail(e, fn) {
Caver.findOne({
Contact: e
}).done(function(err, user) {
// Error handling
if (err) {
return fn(null, null);
// The User was found successfully!
}else{
return fn(null, user);
}
});
}
function md5(string) {
var crypto = require('crypto');
return crypto.createHash('md5').update(string).digest('hex');
}
function getOldGCpassword(login, password) {
return addslashes(md5(login + "*" + password));
}
function addslashes(str) {
// From: http://phpjs.org/functions
// * example 1: addslashes("kevin's birthday");
// * returns 1: 'kevin\'s birthday'
return (str + '').replace(/[\\"']/g, '\\$&').replace(/\u0000/g, '\\0');
}
// Passport session setup.
// To support persistent login sessions, Passport needs to be able to
// serialize users into and deserialize users out of the session. Typically,
// this will be as simple as storing the user ID when serializing, and finding
// the user by ID when deserializing.
passport.serializeUser(function(sessionUser, done) {
done(null, sessionUser.id);
});
passport.deserializeUser(function(id, done) {
findById(id, function (err, user) {
done(err, user);
});
});
// Use the LocalStrategy within Passport.
// Strategies in passport require a `verify` function, which accept
// credentials (in this case, a email and password), and invoke a callback
// with a user object.
passport.use(new LocalStrategy({
usernameField: 'email'
},
function(email, password, done) {
// asynchronous verification, for effect...
process.nextTick(function () {
// Find the user by email. If there is no user with the given
// email, or the password is not correct, set the user to `false` to
// indicate failure and set a flash message. Otherwise, return the
// authenticated `user`.
findByEmail(email, function(err, user) {
if (err) {
return done(err);
}
if (!user) {
return done(null, false, { message: 'Unknown user ' + email });
}
var hash = getOldGCpassword(user.Login, password);
if (hash.localeCompare(user.Password) == 0) {
var returnUser = { email: user.Contact, createdAt: user.Date_inscription, id: user.Id };
return done(null, returnUser, { message: 'Logged In Successfully'} );
} else {
return done(null, false, { message: 'Invalid Password'});
}
})
});
}
));

Resources