I am trying to create a simple node.js app with passport local authentication using mongoDb and express as a framework,but I'm having a problem
Whenever I try to submit the data into database using registration form,after clicking submit it appears in node terminal immediately :
here is how my user schema looks like:
var mongoose = require('mongoose');
var bcrypt = require('bcrypt-nodejs');
// define the schema for our user model
var userSchema = mongoose.Schema({
local : {
name : String,
username : String,
mobile : Number,
email : String,
gender : String,
password : String
}
});
// methods ======================
// generating a hash
userSchema.methods.generateHash = function(password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
};
// checking if password is valid
userSchema.methods.validPassword = function(password) {
return bcrypt.compareSync(password, this.local.password);
};
// create the model for users and expose it to our app
module.exports = mongoose.model('User', userSchema);
and my router file :
// process the signup form
app.post('/signup', passport.authenticate('local-signup', {
successRedirect : '/profile', // redirect to the secure profile section
failureRedirect : '/signup', // redirect back to the signup page if there is an error
failureFlash : true // allow flash messages
}));
passport configuration for sign up logic :
passport.use('local-signup', new LocalStrategy({
nameField : 'name',
usernameField : 'username',
mobileField : 'mobile',
emailField : 'email',
genderField : 'gender',
passwordField : 'password',
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, name, username, mobile, email, gender, 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.name = name;
newUser.local.username = username;
newUser.local.mobile = mobile;
newUser.local.email = email;
newUser.local.gender = gender;
newUser.local.password = newUser.generateHash(password);
// save the user
newUser.save(function(err) {
if (err)
throw err;
return done(null, newUser);
});
}
});
});
}));
I am new to node.js as well as in mongoDb please help me out
Thanks
Reason: reason behind this error is invalid type to store in db. Like mobile is number type but if you are passing a value which can not be convert to number then it will give the same error.
console.log(newUser); before saving the user and check the value you are passing in mobile field is convertible to Number as its data type is number in your schema.
If mobile is "" or undefined or null i.e. not convertible to number then it will not work. Remove this key from object if it's value doesn't exists. Do not pass the undefined,null or "" or string(which can not be convert to number).
Related
I'm using Mysql Database for my Node Js Application. Using the Passport for the Authentication. This is my Passport.js File.
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var mysql = require('mysql');
var connection = mysql.createConnection({
host : "localhost",
user : "root",
password : "",
database: "cafe"
});
connection.connect(function(err){
if(err) throw err;
else console.log("Passport Server Connected");
});
passport.serializeUser(function(user, done) {
console.log("In Serialize !"+ user.ID);
done(null, user.id);
});
// used to deserialize the user
passport.deserializeUser(function(id, done) {
connection.query("select * from user where id = "+id,function(err,rows){
console.log("Inside Deserialize ---> "+rows[0]);
done(err, rows[0]);
});
});
// https://gist.github.com/manjeshpv/84446e6aa5b3689e8b84
// Passport with mysql database
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) {
connection.query("select * from user where email = '"+email+"'",function(err,rows){
console.log(rows);
console.log("above row object");
if (err)
return done(err);
if (rows.length > 0) {
// return done(null, false, req.flash('signupMessage', 'That email is already taken.')); // Not Working
return done(null, false, {message : 'Email Id Already Taken !'}); //Default Json Unauthorised
} else {
// if there is no user with that email
// create the user
var newUserMysql = new Object();
newUserMysql.email = email;
newUserMysql.password = password; // use the generateHash function in our user model
console.log(newUserMysql);
var insertQuery = "INSERT INTO user ( email,password ) VALUES ('"+ email +"','"+ password +"')";
console.log(insertQuery);
connection.query(insertQuery,function(err,rows){
newUserMysql.id = rows.insertId;
if(err) throw err;
// console.log("Error is "+ err);
// console.log(insertQuery);
return done(null, newUserMysql);
});
}
});
// connection.end();
}));
passport.use('local-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
connection.query("SELECT * FROM `user` WHERE `email` = '" + email + "'",function(err,rows){
if (err)
return done(err);
if (!rows.length) {
// return done(null, false, req.flash('loginMessage', 'No user found.')); // req.flash is the way to set flashdata using connect-flash
return done(null, false, {message: 'No User Found! '});
}
// if the user is found but the password is wrong
if (!( rows[0].password == password )){
// return done(null, false, req.flash('loginMessage', 'Oops! Wrong password.')); // create the loginMessage and save it to session as flashdata
return done(null, false, {message: 'Oops! Wrong Password! '});
}
// all is well, return successful user
console.log(" Inside callback of local-login -> "+rows[0]);
return done(null, rows[0]);
});
}));
// module.exports;
As per my Application, Whenever I'm creating a new User in Signup Page, it's Successfully creating the User and the session is created by calling the Serialize and Deserialize function.
But when I try to LogIn the user, it's creating this Error.
Only Serialize is working and Deserialize function is not called in Login Process.
However if I disable the Session with session:false, it's logging me in, but without session which I don't want.
Here's my routes file.
var express = require('express');
var router = express.Router();
async = require('async');
var csrf = require('csurf');
var passport = require('passport');
var csrfProtection = csrf();
router.use(csrfProtection);
// Profile Routes
router.get('/profile',function(req,res,next){
res.render('user/profile');
});
// SIGN UP Routes
router.get('/signup',function(req,res,next){
var messages = req.flash('error');
// console.log("In Get Route "+ messages +" is the Error"); //req.flash not working.
res.render('user/signup', {csrfToken:req.csrfToken(), messages: messages , hasError: messages==undefined ?false :messages.length>0});
});
router.post('/signup',passport.authenticate('local-signup',{
successRedirect:'/user/profile',
faliureRedirect : '/user/signup',
// faliureMessage:'Not Valid',
faliureFlash:true,
// session:false
}));
//Sign In
router.get('/signin',function(req,res,next){
var messages = req.flash('error');
// console.log("In Get Route "+ messages +" is the Error"); //req.flash not working.
res.render('user/signin', {csrfToken:req.csrfToken(), messages: messages , hasError: messages==undefined ?false :messages.length>0});
});
router.post('/signin',passport.authenticate('local-login',{
successRedirect:'/user/profile',
faliureRedirect : '/user/signin',
faliureFlash:true,
// session:false,
}));
//Log Out
router.get('/logout',function(req,res,next){
req.logOut();
res.redirect('/');
});
module.exports = router;
If I may ask, my flash isn't also working. Any Help on that will be appreciated.
PS - Please note that my Sign Up is working fine, so basically the session is being created and serialize and deserialize functions are also working fine. The problem is in Login Session only
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)
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 have my parent schema defined like this:
User.js:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var PasswordSchema = require('./Password');
var UserSchema = new Schema({
name: { type: String, required: true },
password: PasswordSchema
});
mongoose.model('User', UserSchema);
My children schema defined like this:
Password.js:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var crypto = require('crypto');
var PasswordSchema = new Schema ({
_id: false,
hashedPassword: { type: String, required: true },
salt: { type: String, default: '' }
});
var passwordRegex = /^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9]).{8,24}$/;
PasswordSchema.virtual('password')
.set(function (password) {
if (passwordRegex.test(password))
{
this.invalidate('password', 'Invalid password format');
}
});
mongoose.model('Password', PasswordSchema);
module.exports = PasswordSchema;
Now I used these Models schema in my controller like this:
user.js:
require('../models/User');
var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');
var User = mongoose.model('User');
var Password = mongoose.model('Password');
router.post('/register', function (req, res, next) {
var user = new User(req.body);
var password = new Password({ password: 'abcd1234' });
console.log(password.$__.validationError.errors['hashedPassword']); // Here it works I got the validation error
user.password = password;
user.password.$__.validationError = password.$__.validationError; // WORKAROUND
console.log(user.password.$__.validationError.errors['hashedPassword']); // Here it doesn't work no validation error anymore ...
user.save(function (err) {
if (err)
console.log(":(");
else
console.log(":)");
});
});
module.exports = router;
Question:
So my problem now is that no matter what password I send to my children virtual it doesn't invalidate the process. How could I invalidate the mongoose save action from a children virtual ? Is there an other better option ?
Question Updated:
In user.js why the variable password has the validation error and when I assign it to user.password I don't have the validation error anymore ? How can I correct it ?
** Update 2:**
I have found a workaround see user.js: I just assign the required property to generate validation error. But it looks really not clean is there another way?
Here is one good example https://gist.github.com/swaj/1350041, refactor it as below
PasswordSchema.virtual('password')
.get(function(){
return this._password;
})
.set(function (password) {
this._password = password;
// invoke crypto to hash and encrypt password, then assign it to hashedPassword
this.hashedPassword = password; // this is just for test
});
PasswordSchema.path('hashedPassword').validate(function(v) {
if (v) {
if (passwordRegex.test(v)) {
this.invalidate('password', 'Invalid password format');
}
}
if (!v) {
this.validate('password', 'password required');
}
}, null);
Test codes
var user = new User({name: 'dd'});
user.password = new Password({password: 'asdfASF123444'});
user.save(function (err) {
if (err)
console.log(err);
else
console.log("save user successfully");
});
Validation error is
{ [ValidationError: User validation failed]
message: 'User validation failed',
name: 'ValidationError',
errors:
{ password:
{ [ValidatorError: Invalid password format]
properties: [Object],
message: 'Invalid password format',
name: 'ValidatorError',
kind: 'user defined',
path: 'password',
value: undefined } } }
Per invalidate source code
Document.prototype.invalidate = function (path, err, val) {
if (!this.$__.validationError) {
this.$__.validationError = new ValidationError(this);
}
// ...
We know invalidate function belong to Document.
password.$__.validationError.errors['hashedPassword']
You define the validation for PasswordSchema, not in UserSchema. so user.password.$__.validationError.errors['hashedPassword'] is not valid.
Test your code with
var user = new User({name: 'dd'});
user.password = new Password({password: 'asdfwe32113'});
user.save(function (err) {
if (err)
console.log(err);
else
console.log("save user successfully");
});
Validation will be triggered, however, with this code
`user.password = new Password({hashedPassword: 'asdfwe32113'});`
This validation is NOT triggered.
Because for virtual field, only the correct virtual name field is updated then the .set function could be called.
Also please add those codes to virtual('password'), to make sure the hashedPassword could be set correctly.
if (passwordRegex.test(password)) {
this.invalidate('password', 'Invalid password format');
}else {
this.hashedPassword = password;
}
For the second question, require('../models/User'); must be invoked before mongoose.model() to make sure the User.js is parsed firstly, and the User could be added into mongoose.model in User.js. So in user.js could find this User model from mongoose. JavaScript is an interpreted programming language, so we should tell the JS engine the file parsed order in this way.
I have taken the code from here:
link
Snapshot of error:
I get error at newUser.local.email = email;
of this code:
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);
});
}
});
});
}));
In Sample------> models.
I have User.js that contains the schema for User sign up.
and looks like this:
var userSchema = new mongoose.Schema({
email: String,
password: String,
});
What looks wrong here?Can I please get some directions?