I have an app that uses the MEAN stack, recently I have seen a little strange behaviour. Now this doesn't happen every time a user registers, so far it has happened 3 times. When a user registers the app creates 2 accounts for that user with all the same details. Now I have already added functionality to detect if a user already exists with that email and redirect them towards the login page but doesnt seem to stopping the issue.
Heres my code:
// =========================================================================
// 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
firstNameField: 'firstName',
lastNameField: 'lastName',
usernameField: 'email',
passwordField: 'password',
jobTitleField: 'jobTitle',
startDateField: 'startDate',
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({'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, {
message: 'That email is already taken.'
});
}
else { var token = crypto.randomBytes().toString();
// if there is no user with that email
// create the user
var newUser = new User();
// set the user's local credentials
newUser.firstName = req.body.firstName;
newUser.lastName = req.body.lastName;
newUser.email = email;
newUser.password = newUser.generateHash(password); // use the generateHash function in our user model
newUser.jobTitle = req.body.jobTitle;
newUser.startDate = req.body.startDate;
newUser.birthday = req.body.birthday;
newUser.region = req.body.region;
newUser.sector = req.body.sector;
newUser.accountConfirmationToken = token;
newUser.accountConfirmationTokenExpires = Date.now() + 3600000;
newUser.accountVerified = 'false';
newUser.isLineManager = 'false';
// save the user
newUser.save(function(err) {
if (err)
throw err;
else {
var data = {
from: 'system',
to: email,
subject: 'Account Verification',
text: 'You recently registered onto the App, to gain access to your account please verify your account.\n\n' +
'Please click on the following link, or paste this into your browser to complete the process:\n\n' +
'http://' + req.headers.host + '/verify/' + token + '\n\n'
};
mailgun.messages().send(data, function(error, body) {
console.log(body);
console.log("setting token 1");
req.flash('info', 'An e-mail has been sent to ' + email + ' with further instructions.');
});
return done(null, newUser);
}
});
}
});
}));
My Conclusions:
I tested the app by creating a test account and once I had filled out the signup form I quickly clicked twice on the signup-now button and when I checked the database it had created 2 accounts with the same details. Basically it sends 2 POST requests to create accounts and both of them get approved. When only 1 should be approved.
My Question:
How can I fix this issue so if the user clicks twice on the signup
button it only creates one account.
Also could there be another reason this might be happening, is there
any issue with the code above?
Thanks.
Edit:
App Config Code:
// configuration ===============================================================
mongoose.connect(database.url); // connect to mongoDB database on modulus.io
require('./config/passport')(passport);
app.use(express.static(__dirname + '/public'));
app.use(express.static(__dirname + '/views')); // set the static files location /public/img will be /img for users
app.use(busboy());
app.use(compression()); //use compression
app.use(morgan('dev')); // log every request to the console
app.use(bodyParser.urlencoded({'extended': true})); // parse application/x-www-form-urlencoded
app.use(bodyParser.json()); // parse application/json
app.use(bodyParser.json({ type: 'application/vnd.api+json' })); // parse application/vnd.api+json as json
app.use(methodOverride());
app.use(cookieParser()); // read cookies (needed for auth)
app.set('view engine', 'ejs'); // set up ejs for templating
// required for passport
app.use(session({ secret: ''})); // session secret
app.use(passport.initialize());
app.use(passport.session()); // persistent login sessions
app.use(flash()); // use connect-flash for flash messages stored in session
Edit:
Route code:
app.post('/signup', function(req, res, next) {
passport.authenticate('local-signup', function(err, user, info) {
if (err) {
return next(err);
}
if (!user) {
return res.json({
message: 'An account with this email is already registered. Please try to login, if you cant remeber the password then please use our password reset service'
})
}
req.logIn(user, function(err) {
if (err) {
return next(err);
}
return res.json({
redirectUrl: '/verifyAccount',
message: 'We have sent an email to verify your email. Once you have verified your account you will be able to login'
});
});
})(req, res, next);
});
You can disable the signup button on first press to prevent double click on it.
As a solution I can advice you to set unique constraint on your email column, that won't to allow to insert rows with an existing email
And what about the code that uses LocalStrategy, it should work correctly, but you can just override usernameField, passwordField(So, you can remove other fields). Use req.body in order to get other form data as you've already done (Just in case of refactoring)
Related
I was not able to secure individual routes of my adminpanel using passport.js
User Signup is working. Even when I login into panel its successfully redirectly . But the req.isAuthenticate is always returning false value. Hence, I am not able to access routes inside admin panel
Codes
controller/admin.js
var express = require('express'),
router = express.Router(),
session=require('express-session');
module.exports = function (app) {
app.use('/', router);
};
var passport = require('passport');
var flash = require('connect-flash'),
session = require('express-session');
router.use(session({ secret: 'ilovescotchscotchyscotchscotch' ,saveUninitialized: true, resave: true})); // session secret
router.use(passport.initialize());
router.use(passport.session()); // persistent login sessions
router.use(flash());
router.get('/expoadmin/', function(req, res) {
// render the page and pass in any flash data if it exists
res.render('expoadmin/login', { message: req.flash('loginMessage')});
});
// process the login form
router.post('/expoadmin/login', passport.authenticate('admin-login', {
successRedirect : '/expoadmin/dashboard', // redirect to the secure profile section
failureRedirect : '/expoadmin/', // redirect back to the signup page if there is an error
failureFlash : true // allow flash messages
}));
router.get('/expoadmin/logout', function(req, res){
console.log('logging out');
req.logout();
res.redirect('/expoadmin');
});
router.get('/expoadmin/addadmin', function(req, res) {
// render the page and pass in any flash data if it exists
res.render('expoadmin/signup', { message: req.flash('signupMessage') });
});
// process the signup form
router.post('/expoadmin/signup', passport.authenticate('admin-signup', {
successRedirect : '/expoadmin/admins', // redirect to the secure profile section
failureRedirect : '/expoadmin/addadmin', // redirect back to the signup page if there is an error
failureFlash : true // allow flash messages
}));
var fetch =require('../adminroutes/eventsfetch.js');
router.get('/expoadmin/dashboard', isLoggedIn,
function (req, res, next) { res.render('expoadmin/index',{ layout : 'dashboard'}); });
router.get('/expoadmin/eventsfetch', isLoggedIn, fetch.view );
// route middleware to make sure
function isLoggedIn(req, res, next) {
var ses=req.session;
console.log(req.user);
console.log(session.user);
// if user is authenticated in the session, carry on
if (req.isAuthenticated())
return next();
// if they aren't redirect them to the home page
res.redirect('/expoadmin');
}
passport.js
// config/passport.js
// load all the things we need
var LocalStrategy = require('passport-local').Strategy;
var bcrypt = require('bcrypt-nodejs');
// load up the user model
var Admin = require('../app/models/admin');
// expose this function to our app using module.exports
module.exports = function(passport) {
// =========================================================================
// passport session setup ==================================================
// =========================================================================
// required for persistent login sessions
// passport needs ability to serialize and unserialize users out of session
// used to serialize the user for the session
passport.serializeUser(function(user, done) {
done(null, user._id);
});
// used to deserialize the user
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
// =========================================================================
// Admin 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('admin-login', 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) { // 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
Admin.findOne({ 'local.email' : email }, function(err, user) {
// if there are any errors, return the error before anything else
console.log(user);
if (err)
return done(err);
// if no user is found, return the message
if (!user)
return done(null, false, req.flash('loginMessage', 'No admin found.')); // req.flash is the way to set flashdata using connect-flash
// if the user is found but the password is wrong
console.log(bcrypt.compareSync(password, user.local.password));
if (!bcrypt.compareSync(password, user.local.password))
return done(null, false, req.flash('loginMessage', 'Oops! Wrong password.')); // create the loginMessage and save it to session as flashdata
console.log(user);
// all is well, return successful user
return done(null, user);
});
}));
};
router.post('/expoadmin/login',function(req,res,next){
passport.authenticate('admin-login', function (err, user, info) {
if (err) {
//send error message here
}
// Generate a JSON response reflecting authentication status
if (!user) {
//send if user not found
}
else{
req.logIn(user, function (err,data) {
if (err) {
//some error with serialization
}
//do your stuff with info here
res.redirect('/expoadmin/dashboard')
});
});
}
})(req, res, next);
})
you callback will be received here in (err,user,info)
send final req as
return done(null,false,user)
now check req.isAuthenticated
I have been looking at some passport.js tutorials online but haven't grasped a clear understanding of what is happening. Can someone help me clear my doubts below? Please read the paragraph at the bottom first.
So assuming I set up everything correctly, this is the login strategy:
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 error & 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'));
}
// User and password both match, return user from
// done method which will be treated like success
return done(null, user);
}
);
}));
Now in my app.js (server) I have this as one of my routes:
/* Handle Login POST */
router.post('/login', passport.authenticate('login', {
successRedirect: '/home',
failureRedirect: '/',
failureFlash : true
}));
Now in my AJS file:
app.controller('loginController', function($scope) {
var user = $resource('/login');
$scope.createUser = function() {
var User = new user();
User.username = $scope.usernameVar;
User.password = $scope.passwordVar;
User.save();
}
});
Please read this first (Instead of going through the code first):
So when the user clicks on the login button on the login page the createUser function above is run (in my AJS file). Then I create a resource object for the endpoint '/login' and when I call save on that it will run the route for that '/login' endpoint on my server (app.js). Then in my server it will passport.authenticate('login', ... which will run the passport middleware.
Now my question is:
In the passport.use('login'... strategy where the do values for the variables req, username, and password come from in the callback to that strategy. Do I have to explicitly pass the username and password the user enters in the textfield on my front end. Like I have a two way data binding for those two textfields in AJS view. If so how do I pass those username and password values?
Do these two lines in my AJS controller User.username = $scope.usernameVar; and User.password = $scope.passwordVar; attach the usernameVar and passwordVar values to the req object on my server for the route '/login'?
If you have a form with action to post to your path /login, and have input names labeled after your username and password, the submit button will pass the values along to your passport code.
Check out Form in the docs.
I've hit a bit of a problem getting client-session middleware working in Express. In short the session_state doesn't seem to be accessible when redirecting to new route after being set. For reference I have followed this video tutorial (client-session part starts 36:00 approx.) and double checked my steps but still encountering problems. Middleware is set up as follows:
var sessions = require('client-sessions');
Instantiated with code from the Express website.
app.use(sessions({
cookieName: 'session',
secret: 'iljkhhjfebxjmvjnnshxhgoidhsja',
duration: 24 * 60 * 60 * 1000,
activeDuration: 1000 * 60 * 5
}));
I have the sessions middleware placed between bodyParser and routes if that makes any difference.
Here are the sections my routes/index.js pertaining to the issue. The req.session_state seems to get set ok and the correct user details log to the console.
// POST login form
router.post('/login', function(req, res) {
User.findOne( { email: req.body.email }, function(err,user){
if(!user) {
console.log("User not found...");
res.render('login.jade',
{ message: 'Are you sure that is the correct email?'} );
} else {
if(req.body.password === user.password) {
// User gets saved and object logs correctly in the console
req.session_state = user;
console.log("session user...", req.session_state);
res.redirect('/dashboard');
}
}
//res.render('login.jade', { message: 'Invalid password'} );
});
});
However, something is going wrong when the res.redirect('/dashboard'); is run because the session_state is not accessible when it hits that route. Here is the code for the /dashboard route.
router.get('/dashboard', function(req, res) {
// OUTPUT = 'undefined' ???
console.log("dash...", req.session_state);
// 'if' fails and falls through to /login redirect
if(req.session && req.session_state){
console.log("dash route...", req.session_state);
User.findOne( { email: req.session_state.email }, function
(err, user){
if(!user){
req.session.reset();
res.redirect('/login');
} else {
res.locals.user = user;
res.render('dashboard.jade')
}
});
} else {
res.redirect('/login');
}
//res.render('dashboard', { title: 'Your Dashboard' });
});
Basically, the object stored in session_state is not accessible after the /dashboard redirect. I've been trying to debug it for a day or so without any luck. Any help much appreciated. Sorry if I am missing something obvious. Just getting my feet wet with session middleware so maybe I haven't fully grasped Session or I am overlooking something. Thanks in advance!
I've updated my answer with code that should help you set the cookie and an alternative session manager known as a token. In this example I've provided to parts to a middle ware one part which attaches the cookie (this can be expanded on to determine your use case) and the second part which checks the token for expiration or what ever else might be in there (i.e. audience, issuer, etc.)
app.use('/js/',function(req, res, next) {
//check if the request has a token or if the request has an associated username
if(!req.headers.cookie){
console.log('no cookies were found')
var token = jwt.sign({user_token_name:req.body.user_name},app.get('superSecret'), {
expiresIn: 1 *100 *60 // expires in 1 mintue can be what ever you feel is neccessary
});
//make a token and attach it to the body
// req.body.token = token // this is just placing the token as a payload
res.cookie('austin.nodeschool' , token,{ maxAge: 100000, httpOnly: true }) //this sets the cookie to the string austin.nodeschool
}
if(req.body.user_name){
next()
}else{
res.send('request did not have a username').end() // this is here because my middleware also requires a username to be associated with requests to my API, this could easily be an ID or token.
}
},function(req, res, next) {
// console.log(req.headers) this is here to show you the avilable headers to parse through and to have a visual of whats being passed to this function
if(req.headers.cookie){
console.log(req.headers.cookie) //the cookie has the name of the cookie equal to the cookie.
var equals = '=';
var inboundCookie = req.headers.cookie
var cookieInfo = splitCookie(inboundCookie,equals) //splitCookie is a function that takes the inbound cookie and splits it from the name of the cookie and returns an array as seen below.
console.log(cookieInfo)
var decoded = jwt.verify(cookieInfo[1], app.get('superSecret'));
console.log(decoded)
// You could check to see if there is an access_token in the database if there is one
// see if the decoded content still matches. If anything is missing issue a new token
// set the token in the database for later assuming you want to
// You could simply check if it's expired and if so send them to the login if not allow them to proceed through the route.
}
next()
});
I am currently having a problem authenticating user in my SailsJS app using the PassportJS.
I have generated all the stuff needed for authentication using sails-generate-auth according to this tutorial.
It seems like the POST request is routed correctly as defined in the routes.js file:
'post /auth/local': 'AuthController.callback',
'post /auth/local/:action': 'AuthController.callback',
In AuthController I've got following code:
callback: function (req, res) {
function tryAgain (err) {
// Only certain error messages are returned via req.flash('error', someError)
// because we shouldn't expose internal authorization errors to the user.
// We do return a generic error and the original request body.
var flashError = req.flash('error')[0];
if (err && !flashError ) {
req.flash('error', 'Error.Passport.Generic');
} else if (flashError) {
req.flash('error', flashError);
}
req.flash('form', req.body);
// If an error was thrown, redirect the user to the
// login, register or disconnect action initiator view.
// These views should take care of rendering the error messages.
var action = req.param('action');
switch (action) {
case 'register':
res.redirect('/register');
break;
case 'disconnect':
res.redirect('back');
break;
default:
res.redirect('/login');
}
}
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);
}
// Mark the session as authenticated to work with default Sails sessionAuth.js policy
req.session.authenticated = true
// Upon successful login, send the user to the homepage were req.user
// will be available.
res.redirect('/user/show/' + user.id);
});
});
},
I am submitting my login form in Jade template:
form(role='form', action='/auth/local', method='post')
h2.form-signin-heading Please sign in
.form-group
label(for='username') Username
input.form-control(name='username', id='username', type='username', placeholder='Username', required='', autofocus='')
.form-group
label(for='password') Password
input.form-control(name='password', id='password', type='password', placeholder='Password', required='')
.form-group
button.btn.btn-lg.btn-primary.btn-block(type='submit') Sign in
input(type='hidden', name='_csrf', value='#{_csrf}')
I have checked the values passed to the callback function specified for password.callback(..) call:
passport.callback(req, res, function (err, user, challenges, statuses) {
the user variable is set to "false". I suppose, this is where the error comes from.
What is interesting is, that when the callback() function is called after user registration, the user variable is set right to user object containing all the values like username, email, etc.
If you would like to check other source files, my project is available on github in this repository.
Any help is appreciated.
Shimon
You're using identifier as the usernameField for LocalStrategy (i.e. the default setting) and have username in the login view, which means the authentication framework receives no username and fires a Missing Credentials error.
Either change the field name in the login view to identifier or set the appropriate usernameField through the passport config file (config/passport.js):
local: {
strategy: require('passport-local').Strategy,
options: {
usernameField: 'username'
}
}
this is probably some basic mistake but I am watching tutorial and even though I think I done everything exactly like I should after submitting login form I am redirected to the "failureRedirect" page. When I looked at source code in passport module I something.
After this:
Strategy.prototype.authenticate = function(req, options) {
options = options || {};
var username = lookup(req.body, this._usernameField) || lookup(req.query, this._usernameField);
var password = lookup(req.body, this._passwordField) || lookup(req.query, this._passwordField);
//I added:
console.log("U-> " + username);
console.log("P-> " + password);
console says
U-> null
P-> null
Then after this, rest is not executed.
if (!username || !password) {
return this.fail({ message: options.badRequestMessage || 'Missing credentials' }, 400);
}
I am not sure which parts of code should I post here. Maybe this can help
passport.use(new LocalStrategy(
function(username, password, done){
console.log("passport.use new LocalStrategy"); //never gets executed
//never gets executed
User.getUserByUsername(username, function(err, user){
if (err) throw err;
if(!user) {
console.log("Unknown user");
return done(null, false, {message: "Uknkown User"});
}
User.comparePassword(password, user.password, function(err, isMatch){
if (err) throw err;
if (isMatch) {
return done(null, user);
} else {
console.log("invalid Pass");
return done(null, false, {message: "Invalid Password"});
}
});
});
}));
router.post("/login", passport.authenticate("local", {failureRedirect:"/users/login/err", failureFlash:"invalid username or pass"}), function(req, res){
console.log("Authenticated OK");
req.flash("success", "You are logged in");
res.redirect("/xx");
});
I am not sure about the exact implementation that you are doing. Probably you are overriding the authenticate functionality using the prototype pattern.
However, Authentication using Passportjs is simple. I have done one recently in my side project. Please go through the below link with my own experience on implementing Passportjs
I have a well documented artcile that i wrote on my tech blog. Hope this helps you
// complete code for the exmaple node rest api authentication using passport
var express = require('express');
var passport = require('passport');
var passportHttp = require('passport-http');
var basicStrategy = passportHttp.BasicStrategy; // using the basic authentication
var app = express();
app.get('/',function(req,res){
res.send("There you go");
});
app.use(passport.initialize()); // initialize and use it in express
passport.use(new passportHttp.BasicStrategy(function(username,password,done) {
if(username === password){
done(null,username); //null means no error and return is the username
}
else{
return done(null,'there is no entry for you!'); // null means nothing to say,
//no error. 2nd is the custom statement business rule
}
}));
// this function hits first when there is an API call.
function ensureAuthenticated(req,res,next){
if(req.isAuthenticated()){
next();
// next redirects the user to the next api function waiting to be executed in the express framework
}else{
res.sendStatus(403); //forbidden || unauthorized
}
};
// this means all the API calls that hit via mydomain.com/api/ uses this authentication.
//session is false, because its a HTTP API call.
// setting this helps passport to skip the check if its an API call or a session web call
app.use('/api',passport.authenticate('basic',{session:false}));
// this is served the user once the authentication is a susccess
app.get('/api/data',ensureAuthenticated,function(req,res){
var somevalue = [{name: 'foo'},
{name: 'bar'},
{name: 'baz'}];
res.send(somevalue);
});
app.listen(3250);
console.log('listening to port on ' + 3250);