rkusa / koa-passport-example

koa-passport usage example
https://github.com/rkusa/koa-passport
262 stars 74 forks source link

How to create signup method #2

Closed bhanuc closed 10 years ago

bhanuc commented 10 years ago

I want to create something like

    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);
                    });
                }

            });    

            });

        }));

    };
            });

        }));

    };
    ser
                    newUser.save(function(err) {
                        if (err)
                            throw err;
                        return done(null, newUser);
                    });
                }

            });    

            });

        }));

    };

        }));

    };
    ser
                    newUser.save(function(err) {
                        if (err)
                            throw err;
                        return done(null, newUser);
                    });
                }

            });    

            });

        }));

    };

How do I implement this and what do I write in route code. Thanks

rkusa commented 10 years ago

The usage of the 'local' strategy is shown in the example. What is your exact problem?

rkusa commented 10 years ago

Ah, okay, you are talking about a sign up and not a login functionality, I read over that. Could you please format your code example properly?

bhanuc commented 10 years ago

sorry it was formatted very poorly ... I am trying to create the signup code in auth.js which can be called from main aplication in routes

cayasso commented 10 years ago

@bhanuc assuming that User is your model (I am using mongoose) and that you are using email (in my case) as username, here is how you can do it:

// Signin with email
passport.use('local-signin', new LocalStrategy({
      usernameField : 'email'
    },
    function using(email, password, done) {
      User.findOne({ 'email': email }, function findOne(err, user) {
        if (err) return done(err);
        if (!user) done(null, false);
        user.verifyPassword(user.local.hash, password, function verifyPassword(err, valid) {
          if (err) return done(err);
          if (!valid) return done(null, false);
          done(null, user);
        });
      });
    }
  ));

Notice the passReqToCallback: true below, with this option you get access to the request object and so to req.body to get your POST data.

// Signup with email
passport.use('local-signup', new LocalStrategy({
      usernameField : 'email',
      passReqToCallback : true
    },
    function using(req, email, password, done) {
      if (!req.user) {
        User.findOne({ 'email': email }, function findOne(err, user) {
          if (err) return done(err);
          if (user) return done(null, false);

          // account information is in req.body
          // you can do your data validation here.
          delete req.body.password;
          user = new User(req.body);
          user.generateHash(password, function generateHash(err, hash) {            
            if (err) return done(err);
            user.hash = hash;
            user.save(function save() {
              if (err) return done(err);
              done(null, user);
            });
          });
        });
      } else {
        user = req.user;
        user.generateHash(password, function generateHash(err, hash) {            
          if (err) return done(err);
          user.hash = hash;
          user.save(function save() {
            if (err) return done(err);
            done(null, user);
          });
        });
      }
    }
  ));

Then use local-signin and local-signup in your routes accordingly with:

passport.authenticate('local-signin', {
    successRedirect: '/account',
    failureRedirect: '/signin'
});

passport.authenticate('local-signup', {
    successRedirect: '/account',
    failureRedirect: '/signup'
});

Hope this helps!

cayasso commented 10 years ago

Here is how your routes can look like:

var parse = require('co-body');

// ...
// ...
var parser = function *parser(next) {
  this.req.body = yield parse(this);
  yield next;
};

// POST /signin
public.post('/signin',
  parser,
  passport.authenticate('local-signin', {
    successRedirect: '/account',
    failureRedirect: '/signin'
  })
);

// POST /signup
public.post('/signup',
  parser,
  passport.authenticate('local-signup', {
    successRedirect: '/account',
    failureRedirect: '/signup'
  })
);
bhanuc commented 10 years ago

Thanks this is awesome :)

bhanuc commented 10 years ago

@cayasso Hi , do I need to add a custom hash function too ? I 'cause I am getting an error why I try to use signup route

cayasso commented 10 years ago

@bhanuc Yes, the code above is just an example but use your own password hashing and verification module. http://node-modules.com/search?q=hash+password the one in the example is https://github.com/dilvie/credential

bhanuc commented 10 years ago

Here is my generate function -->

User.prototype.generateHash = function (password, cb) {
        console.log(cb);
        if (!password) return cb('');
        var encrypred
        var salt = Math.round((new Date().valueOf() * Math.random())) + '';
              this.salt = salt;
        try {
          encrypred = crypto.createHmac('sha1', salt).update(password).digest('hex')
          return cb(encrypred);
        } catch (err) {
          return cb(err);
        }
      }

and my signup-method -->

passport.use('local-signup', new LocalStrategy({
          usernameField : 'email',
          passReqToCallback : true
        },
        function using(req, email, password, done) {
          if (!req.user) {
            User.findOne({ 'email': email }, function findOne(err, user) {
              if (err) return done(err);
              if (user) return done(null, false);

              // account information is in req.body
              // you can do your data validation here.
              delete req.body.password;
              user = new User(req.body);

              user.generateHash(password, function generateHash(err, password) {   
              if (err) return done(err);
              user.hash = password;
              user.save(function save() {
                if (err) return done(err);

               return done(null, user);
              });

              }
                               );
            });
          } else {
            user = req.user;
            user.generateHash(password, function generateHash(err, password) {            
              if (err) return done(err);
              user.hash = password;
              user.save(function save() {
                if (err) return done(err);
                done(null, user);
              });
            });
          }
        }
      ));

but I am getting my route gets stuck when I try to signup

cayasso commented 10 years ago

@bhanuc Callbacks in node has this signature callback(error, result) so change cb(encrypted); to cb(null, encrypted) in your generateHash method.

bhanuc commented 10 years ago

ohh thanks :+1: btw other returns in generateHash method should I also follow the same convention? so what should I return in other callbacks, that is in err I am returning cb(err), would it become cb(err,null) and for (!password) I am returning cb(' '), should I return cb(' ', null). I am confused in this one as both empty string and null are falsey values .

cayasso commented 10 years ago

@bhanuc you can find a great introduction to callbacks here https://github.com/maxogden/art-of-node#callbacks

If you follow node convention the first argument is for error, for example cb(new Error('Invalid password')) I always stick to this to avoid confusion.

// Errors
cb('Something failed'); // not recommended
cb(true); // not recommended
cb(new Error('Something failed')); // be explicit with your error

// Success
cb(null, true);
cb(null, false);
cb(null, user);
cb(null);
cb();

Check passport custom callbacks http://passportjs.org/guide/authenticate/ More on callbacks http://www.devthought.com/2011/12/22/a-string-is-not-an-error/

Hope this helps!

bhanuc commented 10 years ago

@cayasso Thanks for your awesome help :+1: