I want to prevent users who have a password less than 6 chars from being entered into the database, however my current logic is not allowing that. I'm using express-validator.
Despite a password being less than 6 chars, a user is entered into the database. What should i do to prevent a user who has a password less than 6 chars from being entered into the database ?
I'm using knex/bookshelf orm and postgress for the database
routes/users.js
router.post('/register', [
check('password').isLength({ min: 6 }).withMessage('must be at least 6 chars long'),
check('username').custom( value => {
return User.forge({ username: value}).fetch().then( user => {
if(user){
return Promise.reject('Username already in use');
}
})
}),
check('email').custom( value => {
return User.forge({ email: value}).fetch().then( user => {
if(user){
return Promise.reject('Email already in use');
}
})
})
],
(req, res, next) => {
passport.authenticate('register', (err, user, info) => {
if(err){
console.log(err)
}
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(500).send({
error: errors.array()
})
}
if(info !== undefined){
console.log(info.message)
res.status(403).send(info.message)
}else{
req.logIn(user, err => {
const data = {
username: req.body.username.trim(),
password: req.body.password.trim(),
email: req.body.email.trim()
}
// console.log(data);
// debugger;
User.forge({
username: data.username
}).fetch().then( user => {
const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET);
jwt.verify(token, process.env.JWT_SECRET, function(err, data){
console.log(err, data);
})
console.log('user created in db');
res.status(200).send({
message: 'user created',
token: token,
auth: true
});
});
})
}
})(req, res, next);
});
passport.js
passport.use(
'register',
new Local(
{
usernameField: 'username',
passwordField: 'password',
passReqToCallback: true,
session: false,
},
(req, username, password, done) => {
try {
User.forge({username: username}, {email: req.body.email}).fetch().then(user => {
if (user != null) {
console.log('username or email already taken');
return done(null, false, {
message: 'username or email already taken',
});
} else {
bcrypt.hash(password, 12).then(hashedPassword => {
const user = new User({
username: req.body.username,
password: hashedPassword,
email: req.body.email
})
user.save().then( () => {
return done(null, user);
})
});
}
});
} catch (err) {
return done(err);
}
},
),
);
User model
import bookshelf from '../config/bookshelf';
import validator from 'validator';
/**
* Example User Model.
*/
const User = bookshelf.Model.extend({
tableName: 'users',
timestamps: false,
});
export default User;
Your code is checking for validation errors AFTER you create the user.
Code should be:
(req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).send({ error: errors.array() })
}
passport.authenticate('register', (err, user, info) => {
if(err){
console.log(err)
}
...
Also note that you should be responding with a 400 level error code, not 500. 400 is for when the request made was bad (i.e. validation issue), 500 is when the server acts up.
Related
../Passport.js
const LocalStrategy = require("passport-local").Strategy;
const bcrypt = require("bcrypt");
const mongoose = require("mongoose");
const User = require("../models/User.js");
module.exports = function (passport) {
passport.use(
new LocalStrategy(
{ usernameField: "email", passwordField: "password" },
(email, password, done) => {
console.log(email, password);
User.findOne({ email: email }, async function (err, user) {
console.log(password, user.password);
if (err) {
return done(err);
}
if (!user) {
return done(null, false, {
message: "No User found against this email address",
});
} else if (!user.isEmailVerified) {
return done(null, false, {
message: "User needs to verify the OTP",
});
}
var result = await bcrypt.compare(password, user.password);
if (result) {
return done(null, user);
}
if (!result) {
return done(null, false, { message: "Incorrect Password" });
}
});
}
)
);
};
user.js
router.post(
"/login",
validateEmail,
validatePassword,
validator,
(req, res, next) => {
passport.authenticate(
"local",
{ session: false },
function (err, user, info) {
console.log("::Err::", err, "::User::", user, "::Info::", info);
if (err) {
next(new BadRequestResponse(err));
}
if (!user) {
next(new UnauthorizedResponse(info.message, 401));
} else {
next(new OkResponse({ user: user.toAuthJSON() }));
}
}
)(req, res, next);
}
);
I'm struggling since ages to find the issue why bcrypt keeps on returning "false" even though both the hased password and entered password are the same. Please ignore the middlewares after the "/login' router since they are not affecting the entire flow
I implemented a signup route below. It gets to "User saved..." but the request returns 404.
It doesn't seem to be executing the login strategy:
router.post("/signup", function(req, res, next) {
var email = req.body.email;
var password = req.body.password;
User.findOne({ email: email }, function(err, user) {
if (err) { return next(err); }
if (user) {
return res.status(409).send({message: "Duplicate user - already registered."});
}
var newUser = new User({
email: email,
password: password
});
newUser.save(next);
console.log("User saved...");
});
},
passport.authenticate("login"),
function(req, res) {
return res.status(200).send({
message: "Signup successful",
user: req.user
});
}
);
My Passport login strategy looks like this:
passport.use("login", new LocalStrategy(async (email, password, done) => {
console.log("login...");
User.findOne({ email: email }, function(err, user) {
if (err) { return done(err); }
if (!user) {
return done(null, false, { message: "No user has that email!" });
}
user.checkPassword(password, function(err, isMatch) {
console.log("Checked password...");
console.log("Error? Match?");
console.log(err);
console.log(isMatch);
if (err) { return done(err); }
if (isMatch) {
console.log("Returning done...");
return done(null, user, { message: 'Logged in Successfully' });
} else {
return done(null, false, { message: "Invalid password." });
}
});
});
}));
Here's what I see in the logs:
User saved...
POST /signup 400 181.122 ms - -
Passport is likely throwing the 400 error because the username/password fields are not set.
Passport expects username and password and what you are passing are the email and password. So you can modify the code and let passport's LocalStrategy use the email as the username.
You can set the username and password as follows:
passport.use("login", new LocalStrategy({
usernameField: 'email',
passwordField: 'password',
},async (usernameField, passwordField, done) => {
console.log("login...");
User.findOne({ email: usernameField }, function (err, user) {
if (err) { return done(err); }
if (!user) {
return done(null, false, { message: "No user has that email!" });
}
user.checkPassword(passwordField, function (err, isMatch) {
console.log("Checked password...");
console.log("Error? Match?");
console.log(err);
console.log(isMatch);
if (err) { return done(err); }
if (isMatch) {
console.log("Returning done...");
return done(null, user, { message: 'Logged in Successfully' });
} else {
return done(null, false, { message: "Invalid password." });
}
});
});
}));
You can check the line which was throwing the error from passport's source code here
I am new to Node.js.
For my application, I have two different type of users being doctors and patient. Currently, I been coding for the login and registration for patients and it works. I starting to code the login for the doctors and it doesn't seem to be working. Below is the passport.js. I watched a video (https://www.youtube.com/watch?v=6FOq4cUdH8k&ab_channel=TraversyMedia) to learn how to code the login and registration.
I think the issue is within the passport.js, I'm not really sure how to code it, when it comes to the second user.
const LocalStrategy = require('passport-local').Strategy;
const bcrypt = require('bcryptjs');
const Patient = require('../models/patients');
module.exports = function(passport) {
passport.use(
new LocalStrategy({ usernameField: 'email' }, (email, password, done) => {
Patient.findOne({
email: email
}).then(patient => {
if (!patient) {
return done(null, false, { message: 'That email is not registered' });
}
bcrypt.compare(password, patient.password, (err, isMatch) => {
if (err) throw err;
if (isMatch) {
return done(null, patient);
} else {
return done(null, false, { message: 'Password is incorrect' });
}
});
});
})
);
passport.serializeUser(function(patient, done) {
done(null, patient.id);
});
passport.deserializeUser(function(id, done) {
Patient.findById(id, function(err, patient) {
done(err, patient);
});
});
const Doctor = require('../models/doctors');
module.exports = function(passport) {
passport.use(
new LocalStrategy({ usernameField: 'email' }, (demail, dpassword, done) => {
Doctor.findOne({
demail: demail
}).then(doctor => {
if (!doctor) {
return done(null, false, { message: 'That email is not registered' });
}
bcrypt.compare(dpassword, doctor.dpassword, (err, isMatch) => {
if (err) throw err;
if (isMatch) {
return done(null, doctor);
} else {
return done(null, false, { message: 'Password is incorrect' });
}
});
});
})
);
passport.serializeUser(function(doctor, done) {
done(null, doctor.id);
});
passport.deserializeUser(function(id, done) {
Doctor.findById(id, function(err, doctor) {
done(err, doctor);
});
});
}}:
This is my doctor.js in route
const express = require('express');
const router = express.Router();
const bcrypt = require('bcryptjs');
const passport = require('passport');
const Doctor = require('../models/doctors');
router.get('/doctorregister', (req, res) => res.render('Doctorregister'));
router.get('/doctorlogin', (req, res) => res.render('Doctorlogin'));
module.exports = router;
router.post('/doctorregister', (req, res) => {
const { dname, dqualification, dlocation, dpractice, demail, dmobileno, dpassword, dpassword2 } = req.body;
let errors = [];
if (!dname|| !dqualification || !dlocation || !dpractice || !demail || !dmobileno || !dpassword || !dpassword2) {
errors.push({ msg: 'Please enter all fields' });
}
if (dpassword != dpassword2) {
errors.push({ msg: 'Passwords do not match' });
}
if (dpassword.length < 6) {
errors.push({ msg: 'Password must be at least 6 characters' });
}
if (errors.length > 0) {
res.render('doctorregister', {
errors,
dname,
dqualification,
dlocation,
dpractice,
demail,
dmobileno,
dpassword,
dpassword2
});
} else {
Doctor.findOne({ demail: demail }).then(doctor => {
if (doctor) {
errors.push({ msg: 'Email already exists' });
res.render('register', {
errors,
dname,
dqualification,
dlocation,
dpractice,
demail,
dmobileno,
dpassword,
dpassword2
});
} else {
const newDoctor = new Doctor({
dname,
dqualification,
dlocation,
dpractice,
demail,
dmobileno,
dpassword,
});
//hashing password in the database
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(newDoctor.dpassword, salt, (err, hash) => {
if (err) throw err;
newDoctor.dpassword = hash;
newDoctor.save()
.then(doctor => {
req.flash('success_msg', 'You are now registered and can log in')
res.redirect('/doctors/doctorlogin');
})
.catch(err => console.log(err));
});
});
}
});
}
});
router.post('/doctorlogin', (req,res, next) => {
passport.authenticate('local', {
successRedirect: '/doctordashboard',
failureRedirect: '/doctors/doctorlogin',
failureFlash: true
})(req, res, next);
});
router.get('/logout', (req, res) => {
req.logout();
req.flash('You are logged out');
res.redirect('/doctors/doctorlogin');
});
The two different type of users should be different through the user Schema if you are using mongoose and mongoDB as Travesty-Media uses in some of his videos, and through the permissions in different sections of your web app.
Use the same passport code that you have used for the patient user and it works. Passport takes the user info and turns it into a Bearer auth token and sends it to the client, and then when it receives it from the client it decodes it to see what kind of user is logging in...
Different users have different info but same Passport logic, even if there are 2 different patient-users. So the logic from patient to doctor regarding the Passport is the same.
If you want the patient to have access to different routes of your app, you need to insert a middleware to that route to check if the type of user is the same with what you want it to be, but after passport has got the info from the token it has received from the client side.
First of all, let me give you a warm thank you for giving a thought to this question.
So, what's the problem?
(This is a simple problem for most of you grandmasters!)
Well, the user can be registered to this simple app. But, for some reason, authentication doesn't work. That some reason is what my brain nerves having a hard time comprehending!
Having tried all the possible solutions for hours and hours, this novice-newbie decided to head over to the haven of veterans here in the StackOverflow!
Let me give you the code, so you can shed me some bright light!
Following is a capture of the code written for the authentication
//Authenticating the user
router.post('/authenticate', (req, res, next) => {
const username = req.body.username;
const password = req.body.password;
User.getUserByUsername(username, (err, user) => {
if (err) throw err;
if (!user) {
return res.json({
sucess: false,
msg: 'There is no such user found here'
});
}
User.comparePassword(password, user.password, (err, isMatch) => {
if (err) throw err;
if (isMatch) {
const token = jwt.sign(user.toJSON(), config.secret, {
expiresIn: 604800 // 1 week
});
res.json({
success: true,
token: 'JWT' + token,
user: {
id: user._id,
name: user.name,
username: user.username,
email: user.email
}
});
} else {
return res.json({
success: false,
msg: 'Enter the correct details!'
});
}
});
});
});
//Getting into the dashboard
router.get('/profile', passport.authenticate('jwt', {
session: false
}),
(req, res, next) => {
res.json({
user: req.user
});
});
The next few pictures on your way shows you the POSTMAN requests that are done by this novice.
Here, a post request is done to register the user and as you can see, there's not a smidgen of a problem there; the user is, without a doubt, registered!
Here's the authentication done with POSTMAN
But now, for some reason (which I have zero clue of), the user is NOT authenticated. This is the problem that I need to solve.
Here is a code of the model/user.js file in case you want to know what's in there as well
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const config = require('../config/database');
// These are the collection or entities in ERD language
const UserSchema = mongoose.Schema({
name: {
type: String
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
}
});
const User = module.exports = mongoose.model('user', UserSchema); //User is the name give for this particular model's schema
// these are functions implemented to do a certain task
module.exports.getUserById = function (id, callback) {
User.findById(id, callback);
}
module.exports.getUserByUsername = function (username, callback) {
const query = {
username: username
}
User.findOne(query, callback);
}
module.exports.addUser = function (newUser, callback) {
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(newUser.password, salt, (err, hash) => {
if (err) throw err;
newUser.password = hash;
newUser.save(callback);
});
})
}
//comparing the hash password
module.exports.comparePassword = function (candidatePasword, hash, callback) {
bcrypt.compare(candidatePasword, hash, (err, isMatch) => {
if (err) throw err;
callback(null, isMatch);
});
}
Thank You for your time!
Stay safe btw!
edit1: The code for the registration or signing up.
router.post('/signup', (req, res, next) => {
let newUser = new User({
name: req.body.name,
email: req.body.email,
username: req.body.username,
password: req.body.password
});
User.addUser(newUser, (err, user) => {
//console.log("registration is working");
if (err) {
res.json({
sucess: false,
msg: 'Hey! Enter the correct information man!'
});
} else {
res.json({
success: true,
msg: 'you are registered'
});
}
});
});
Here's the whole routes/users.js file for you to refer
const express = require('express');
const router = express.Router();
const passport = require('passport');
const jwt = require('jsonwebtoken');
const User = require('../models/user');
const config = require('../config/database');
// Signingup the user
router.post('/signup', (req, res, next) => {
let newUser = new User({
name: req.body.name,
email: req.body.email,
username: req.body.username,
password: req.body.password
});
User.addUser(newUser, (err, user) => {
//console.log("registration is working");
if (err) {
res.json({
sucess: false,
msg: 'Hey! Enter the correct information man!'
});
} else {
res.json({
success: true,
msg: 'you are registered'
});
}
});
});
//Authenticating the user
router.post('/authenticate', (req, res, next) => {
const username = req.body.username;
const password = req.body.password;
User.getUserByUsername(username, (err, user) => {
if (err) throw err;
if (!user) {
return res.json({
sucess: false,
msg: 'There is no such user found here'
});
}
User.comparePassword(password, user.password, (err, isMatch) => {
if (err) throw err;
if (isMatch) {
const token = jwt.sign(user.toJSON(), config.secret, {
expiresIn: 604800 // 1 week
});
res.json({
success: true,
token: 'JWT' + token,
user: {
id: user._id,
name: user.name,
username: user.username,
email: user.email
}
});
} else {
return res.json({
success: false,
msg: 'Enter the correct details!'
});
}
});
});
});
//Getting into the dashboard
router.get('/profile', passport.authenticate('jwt', {
session: false
}),
(req, res, next) => {
res.json({
user: req.user
});
});
router.post('/login', (req, res, next) => {
let newUser = new User({
username: req.body.username,
password: req.body.password
});
User.addUser(newUser, (err, user) => {
if (err) {
res.json({
success: false,
msg: "Enter the correct information"
});
} else {
res.json({
success: true,
msg: "User loggedIn"
});
}
});
});
module.exports = router;
In your schema, you don't have the field username but your query is {username: username}. That's why you can't find any user match and get the response "There is no such user found here". Change your query to {name: username} may solve the problem.
This is passport function to extract headers. I am using fromAuthHeaderWithScheme one, I already tried fromAuthHeaderAsBearerToken with bearer token as well. I could not make it work no matter what?
const JwtStrategy = require('passport-jwt').Strategy,
ExtractJwt = require('passport-jwt').ExtractJwt,
User = require('../models/user'),
Config = require('../config/database');
module.exports = function(passport) {
let opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderWithScheme("JWT");
opts.secretOrKey = Config.secret;
//Code only comes until here.
passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
console.log(jwt_payload);//Code never reaches here.
User.getByUserId({
id: jwt_payload._id
}, function(err, user) {
if (err) {
return done(err, false);
}
if (user) {
return done(null, user);
} else {
return done(null, false);
}
});
}));
}
Next is my getUserById function
module.exports.getByUserId = function(id, cb) {
User.findById(id, cb)
}
Next, is where above two gets called:
router.post('/login', function(req, res) {
let username = req.body.username;
password = req.body.password;
User.getByUserName(username, function(err, user) {
if (err) {
throw err;
}
if (!user) {
return res.json({
success: "false",
msg: "User not found"
})
}
//if found compareUser to regiestred one
User.comparePassword(password, user.password, function(err, isMatched) {
if (err) {
throw err;
}
if (isMatched) {
const token = jwt.sign(user.toJSON(), CONFIG.secret, {
expiresIn: 3600 /*Logout in 1 hour*/
});
res.json({
success: "true",
token: 'JWT ' + token,
user: user._id,
email: user.email,
username: user.username,
});
} else {
return res.json({
success: "false",
msg: " Password not Matched"
});
}
});
});
});
And these are comparePassword and getUserByName incase you need to see:
module.exports.comparePassword = function(typedPassword, hash, cb) {
bcrypt.compare(typedPassword, hash, (err, isMatched) => {
if (err) {
throw err;
}
return cb(null, isMatched);
})
};
module.exports.getByUserName = function(username, cb) {
const query = {
username: username
}
User.findOne(query, cb);
}
The secret key is same every where, that is not the issue. I cannot seem to figure out the issue.
router.get("/profile", passport.authenticate('jwt', {
session: false
}, function(req, res, next) {
res.json({
success: true,
message: "This is user profile",
user: req.user
});
}));
Now, above is how I authenticate, using postman and sending request as content type "Authorization" and The token. Encase, any of you are wondering, I already tried 'bearer '+token through bearer scheme.
I changed the first code block I posted above to this
const JwtStrategy = require('passport-jwt').Strategy,
ExtractJwt = require('passport-jwt').ExtractJwt,
keys = require('./keys'),
mongoose = require('mongoose'),
User = require('../models/User');
const opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken('Bearer');
opts.secretOrKey = keys.secretOrKey;
module.exports = passport => {
passport.use(new JwtStrategy(opts, (jwt_payload, done) => {
User.findOne({ id: jwt_payload.sub }, (err, user) => {
User.findById(jwt_payload.id)
.then(user => {
if (user) {
return done(null, user);
}
return done(null, false);
})
.catch(err => console.log(err));
});
}))
};
And second block to this. Basically change the token from 'JWT' to 'Bearer'.
router.post('/login', (req, res) => {
const email = req.body.email, password = req.body.password;
User.findOne({ email: email })
.then(user => {
if (!user) {
res.status(404).json({ msg: 'User not found' })
}
//Check password
bcrypt.compare(password, user.password)
.then(isMatch => {
if (isMatch) {
//User found
//Create Jwt Payload
const payload = {
id: user.id,
name: user.name,
avatar: user.avatar
}
jwt.sign(
payload,
keys.secretOrKey,
{ expiresIn: 3600 },
(err, token) => {
res.json({
success: true,
token: 'Bearer ' + token
});
});
} else {
return res.status(400).json({ password: 'Password do not match' })
}
})
// .catch(err => console.log(err));
})
});
Now its working for me.