I'm basically trying to create a register page using MEAN and I want to check against the database (Mongo using Mongoose) if the user and email already exists, if one of them is true, send a res.json fail.
And if the username and email are not already in the database, continue to add the user.
I'm getting an error "Can't set headers after they are sent." On the Node console and I'm trying to figure out why.
Once User.getUsername() returns a user in the callback (if the username passed exists in the db), shouldn't it return the json and end the request there? Why is it continuing to the User.adduser() function and trying to set the header there too?
Any help is appreciated, thank you.
router.post('/register', (req, res, next) =>{
let newUser = new User({ // Collect body info
name: req.body.name,
email: req.body.email,
username: req.body.username,
password: req.body.password
});
// Check if username is available
User.getUserByUsername(newUser.username, (err, user) => {
if(err) throw err;
if(user){
return res.json({success: false, msg: 'User already exists'});
// I want to end here if there's a user
}
});
//Continue to add user if getUserByUsername() returns false for user
// Add user
User.addUser(newUser, (err, user) => { // Add user
if(err){
return res.json({success: false, msg: 'Failed to register'});
} else {
return res.json({success: true, msg: 'You have been successfully registered!'});
}
});
});
The logic you have doesn't work for as expected because of how asynchronous methods work. The current logic is do User.getUserByUsername then do User.addUser and if either method calls back, handle it. I'm pretty sure you want, do User.getUserByUsername, wait for it's callback, then call User.addUser if necessary. Here's a crude implementation, you could use promises or define the methods outside of the logic to clean it up. Also, make sure you are calling res.end() at some point in your code.
router.post('/register', (req, res, next) =>{
let newUser = new User({ // Collect body info
name: req.body.name,
email: req.body.email,
username: req.body.username,
password: req.body.password
});
// Check if username is available
User.getUserByUsername(newUser.username, (err, user) => {
if(err) throw err;
if(user){
return res.json({success: false, msg: 'User already exists'});
// I want to end here if there's a user
}else{
//Continue to add user if getUserByUsername() returns false for user
// Add user
User.addUser(newUser, (err, user) => { // Add user
if(err){
return res.json({success: false, msg: 'Failed to register'});
} else {
return res.json({success: true, msg: 'You have been successfully registered!'});
}
});
}
});
});
Related
I'm trying to implement a basic user registration flow using Express.js and mySQL. This is the code I have at the moment (stripped for brevity):
register(app, db) {
app.post('/register', (req, res) => {
let email = req.body.email
let password = req.body.password
try {
// add the user to the database
var q_params = [email, bcrypt.hashSync(password, 9)]
db.query("INSERT INTO users VALUES (?, ?)", q_params, (err) => {
if (err) {
throw err
}
})
} catch (err) {
// respond with an error if one is encountered
res.json({
success: false,
msg: err
})
return
}
// respond with success if everything else goes ok
res.json({
success: true,
email: email
})
});
}
The problem is that no matter the outcome of the code in the try block, I am always getting success: true. How come the error response is never triggered? Is there a better way to be handling this scenario?
I'm assuming the issue is that the nothing is waiting for the db.query() function to finish executing. It's not async and not awaiting that function to resolve. So the last res.json({}) gets hit right away.
I would try to rewrite the code to either use promises or to use that callback function passed to .query(), maybe something like this:
app.post('/register', (req, res) => {
let email = req.body.email
let password = req.body.password
// add the user to the database
var q_params = [email, bcrypt.hashSync(password, 9)]
db.query("INSERT INTO users VALUES (?, ?)", q_params, (err) => {
if (err) {
return res.json({
success: false,
msg: err
});
}
res.json({
success: true,
email: email
});
});
});
I am using mysql with nodejs. I am getting "Error: failed to serialize user into session". When I console.log user in serializeuser function it prints "serialize [object Object]" and in "local-login" strategy it prints RowDataPacket as shown below.
RowDataPacket {
Patient_id: 2,
fname: 'xxx',
mname: 'yyy',
lname: 'zzz',
Email: 'xxx#yyy.com',
pass: '$2b$10$ycZI4SLTJEihxKDHpCXwcOhkmBiOKzmMqFyuEOgXa0KuXYupgaMeG',
Contact: '090078601',
Gender: 'Male',
dateofbirth: 1111-01-13T19:31:48.000Z,
Blood_group: null
}
// used to serialize the user for the session
passport.serializeUser(function(user, done) {
console.log(`serialize ${user}`)
done(null, user.patient_id)
})
// used to deserialize the user
passport.deserializeUser(function(id, done) {
// connection.connect()
connection.query("SELECT * FROM PATIENT WHERE PATIENT_ID = ? ",[id], function(err, rows){
done(err, rows[0])
})
// 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.connect()
connection.query("SELECT * FROM PATIENT WHERE EMAIL = ?",[email],function(err,rows){
if (err)
return done(err)
if (!rows.length) {
return done(null, false, req.flash('error', 'No user found.')) // req.flash is the way to set flashdata using connect-flash
}
bcrypt.compare(password, rows[0].pass, function(err, result) {
if(!result)
{
return done(null, false, req.flash('error', 'Oops! Wrong password.'))
}
// console.log(rows[0])
});
return done(null, rows[0])
})
// connection.end()
}
))
my post login look like this
router.post('/patient/login', ensureGuest, passport.authenticate('local-login', {
successRedirect : '/home', // redirect to the secure profile section
failureRedirect : 'login', // redirect back to the signup page if there is an error
failureFlash : true // allow flash messages
})
);
don't suggest to use session:false while authenticating in post method
The type of object being returned by the sql query was causing the problem, serialize and deserialize accepts json object so I just added stringifyObjects option in my connection configs so that sql query returns json objects and not the default type of object which it usually returns.
module.exports = {
'connection': {
'host': 'localhost',
'user': 'root',
'password': 'fast',
'database': 'hms',
'stringifyObjects': 'Stringify'
}
};
I'm trying to pass a JSON error object into my code using the error function in two cases. Once in the email and password check statement and again in the if existingUser statement. I think it's just that time of the night.
const User = require('../models/user');
exports.signup = function(req, res, next) {
const email = req.body.email;
const password = req.body.password;
if (!email || !password) {
return res.err("Please enter in email and password");
}
//See if a user with the given email exists
User.findOne({ email: email }, function(err, existingUser) {
if (err) { return next(err); }
//If a user with email does exist, return an Error
if (existingUser) {
//the status sets the status of the http code 422 means couldn't process this
return res.err( 'Email is in use' );
}
//If a user with email does NOT exist, create and save user record
const user = new User({
email: email,
password: password
});
user.save(function(err) {
if (err) { return next(err); }
//Respond to request indicating the user was created
res.json({ success: true });
});
});
}
At the moment you are not returning the right status code in your response, you could try this:
Replace:
return res.err("Please enter in email and password");
With
return res.status(422).send({error: "Please enter in email and password"})
And replace:
return res.err( 'Email is in use' );
With:
return res.status(422).send({ error: "Email is in use" });
This will send back the required status code in the http response.
Also consider only using single or double quotes in your code for consistency.
I am trying to build a user signup api using Passport, MySql, NodeJS and Sequelize. The only problem that i face is that when a user has signed up once and he tries to sign up again with the same email user is thrown a 401 Unauthorized Error instead of the user object. When i tried to debug the same the response that i was getting from the server was this
[object SequelizeInstance:users]. The files have been mentioned below. Thanks a tonnn in advance!!!.
Passport.js file:
var LocalStrategy = require('passport-local').Strategy;
var mysql = require('mysql');
var Model = require('../models/models.js');
// 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) {
connection.query("select * from users where id = " + id, function(err, rows) {
done(err, rows[0]);
});
});
// =========================================================================
// 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: 'email',
passwordField: 'password',
passReqToCallback: true // allows us to pass back the entire request to the callback
},
function(req, email, password, done) {
Model.User.findOne({
where: {
email: email
}
}).then(function(user) {
if (user == null) {
Model.User.create({
email: email,
password: password
}).then(function(user) {
return done(null, user);
}).catch(function(err) {
return done(null, err);
});
} else {
return done(null, false);
}
})
}));
};
The Signup api:
router.post('/signup', passport.authenticate('local-signup'), function(req, res) {
// If this function gets called, authentication was successful.
// `req.user` contains the authenticated user.
console.log(req.user);
if(req.user){
res.send({
success: true,
response: 'signup successful'
});
} else {
res.send({
success: false,
response: 'Email already in use'
});
}
});
The User model is:
//models/users.js
var Sequelize = require('sequelize')
var attributes = {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: Sequelize.STRING
},
email: {
type: Sequelize.STRING
},
password: {
type: Sequelize.STRING
},
created_by: {
type: Sequelize.INTEGER
}
}
var options = {
// Add the timestamps attributes (updatedAt, createdAt)
timestamps: true,
// don't delete database entries but set the newly added attribute deletedAt
// to the current date (when deletion was done). paranoid will only work if
// timestamps are enabled
paranoid: true,
// don't use camelcase for automatically added attributes but underscore style
// so updatedAt will be updated_at
underscored: true,
// disable the modification of table names; By default, sequelize will automatically
// transform all passed model names (first parameter of define) into plural.
// if you don't want that, set the following
freezeTableName: true,
// define the table's name
tableName: 'users'
}
module.exports.attributes = attributes
module.exports.options = options
The automated table creation model script is:
// models/models.js
var UserMeta = require('./users.js'),
connection = require('./index.js')
var User = connection.define('users', UserMeta.attributes, UserMeta.options)
// force: true will drop the table if it already exists
User.sync({
force: true,
match: /_servernew$/
}).then(function() {
// Table created
return User.create();
});
// you can define relationships here
module.exports.User = User;
So i came up with the solution. The following code needs to be changed.
router.post('/signup', function(req, res, next) {
passport.authenticate('local-signup', function(err, user, info) {
if(user){
req.logIn(user, function(err) {
if (err) {
return next(err);
} else {
res.send({
success: true,
response: 'signup successful'
});
}
});
}
if(!user){
res.send({
success: false,
response: 'Authentication Failed'
});
}
if(err){
res.send({
success: false,
response: 'Authentication failed'
})
}
})(req, res, next);
});
and the passport.js code should be like this.
// =========================================================================
// 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: 'email',
passwordField: 'password',
passReqToCallback: true // allows us to pass back the entire request to the callback
},
function(req, email, password, done) {
Model.User.findOne({
where: {
email: email
}
}).then(function(user, err) {
console.log('I entered'+user);
console.log('I entered'+err);
if(err) {
console.log(err);
return done(null, false);
}
if(user == null) {
Model.User.create({
email: email,
password: password
}).then(function(user) {
return done(null, user);
}).catch(function(err) {
return done(null, err);
});
}
if(user){
return done(null, false);
}
})
}));
It will work just like a charm :D.
I'm writing a Rest API with Node.js and using JWTs.
I have the route below to authenticate users.
I'd like to ask, the user that is returned from the method User.findOne returns the correct password hence I'm able to check if its correct.
However is this safe to do? I did a console.log and it shows the password (albeit encrypted) but still feels unsafe as someone could surely view?
router.post('/authenticate', function(req, res) {
// find the user
User.findOne({
name: req.body.name
}, function(err, user) {
if (err) throw err;
if (!user) {
res.json({ success: false, message: 'Authentication failed. User not found.' });
} else if (user) {
// check if password matches
if (user.password != req.body.password) {
res.json({ success: false, message: 'Authentication failed. Wrong password.' });
} else {
// if user is found and password is right
// create a token
var token = jwt.sign(user, app.get('superSecret'), {
expiresInMinutes: 1440 // expires in 24 hours
});
// return the information including token as JSON
res.json({
success: true,
message: 'Enjoy your token!',
token: token
});
}
}
});
});
No.
Furthermore, passwords shouldn't be saved encrypted in the database, but hashed. The classical environment saves passwords for example as md5 (more common) or bcrypt (more secure) hash.
This ensures that even if your database gets stolen, no one will have the passwords of your users; there is no way to "decrypt" the hashes (not in a hundred million years).
When a user logs in you compare a hash of the entered password with the hash assigned with the user. You can use great modules like bcrypt-nodejs
EDIT
From a technical aspect it wouldn't be dangerous. When you start your server JavaScript compiles your code and executes the result in a V8 engine. There is no way to access whats ever returned by your database as long as the connection between node and MySQL is save.
One could possibly dump the servers memory and hope to find the right bits. But if someone gains the necessary permissions, you'r doomed either way.
I implemented an example for you, it's untested, but should show you how it's meant.
router.post('/register', function(req, res) {
bcrypt.hash(req.body.password, null, null, function(err, hash) {
if (!err) {
var newUser = new User({
name: req.body.name,
password: hash
});
newUser.save(); // ????
}
});
});
router.post('/authenticate', function(req, res) {
User.findOne({
name: req.body.name
}, function(err, user) {
var password = 'GP%Z!zvbk/9>Ss-R';
var passwordHash = '$2a$10$W.zZPCaNOuR152I4qENKH.8h7I6BPcfCYBJqHPNXbVaBz0XWVxnBm'; // bcrypt of string ')RZK&M(QX"k188cw'
if (user) {
password = req.body.password;
passwordHash = user.password;
}
bcrypt.compare(password, passwordHash, function(err, success) {
if (success) {
var token = jwt.sign(user, app.get('superSecret'), {
expiresInMinutes: 1440
});
res.json({
success: true,
message: 'Enjoy your token!',
token: token
});
}
else {
res.status(401).json({
success: false,
message: 'Authentication failed.'
});
}
});
});
});
Note: bcrypt uses random salt by default for each hash operation. This means, whenever you hash a given input, it will produce different hashes each time. The salt is then stored as part of the hash which can be verified then. Please check Wikipedia for further info.