Joi Validation Sequelize Custom Method - node.js

Im currently trying to validate if the username already exists through Sequelize however currently the promise is returned AFTER the response is sent back to the client meaning Joi thinks the username doesnt exist yet every time. Anyone know how to not have Joi just finish validation prematurely and actually process the query/return the right result.
schema.js
const Joi = require('joi')
const { User } = require('../models')
const validateUsername = (value, helpers) => {
User.findOne({ where: { username: value } }).then(user => {
if (user) {
return helpers.error('username.exists')
}
return value
})
}
const schema = Joi.object({
body: {
username: Joi.string().min(3).required().custom(validateUsername)
.messages({
'string.base': 'Invalid Username',
'string.empty': 'Username Required',
'string.min': 'Username Must Be At Least {#limit} Characters Long',
'any.required': 'Username Required',
'username.exists': 'Username Already In Use'
})
}
})
module.exports = schema
validator.js
const options = {
abortEarly: false,
allowUnknown: true,
stripUnknown: true
}
const validator = (schema) => {
return (req, res, next) => {
const { error } = schema.validate(req, options)
const valid = error == null
if (valid) {
next()
} else {
const { details } = error;
const errors = details.map(i => i.message)
res.status(422).json({ errors })
}
}
}
module.exports = validator

Related

Error: Invalid email/password combination with Node.js and MongoDB

Am trying to login my admin , i defined the login credentials both in the mongodb and in the .env so here is the code which has a problem.
const Admin = require('../models/admin');
const Voters = require('../models/voters');
const bcrypt = require('bcrypt');
exports.checkCredentials = async (req, res, next) => {
const email = req.body.email;
const password = req.body.password;
Admin.findOne({ email: email }).exec(async (error, adminData) => {
if (error) {
// some error occured
return res.status(400).json({ error });
}
if (adminData) {
// email is correct checking for password
const match = await bcrypt.compare(password, adminData.password);
if (match) {
req.adminID = adminData._id;
next();
} else {
return res.status(200).json({
msg: 'Invalid email/password combination yyy',
});
}
} else {
// no data found for given email
return res.status(200).json({
msg: 'Invalid email/password combination !!!!',
});
}
});
};
exports.verifyVoter = async (req, res, next) => {
let query;
if (req.query.voterID) {
query = {
voterID: req.query.voterID,
};
} else {
query = {
phone: req.body.phone,
};
}
console.log(query);
Voters.findOne(query).exec(async (error, voterData) => {
if (error) {
// some error occured
return res.status(400).json({ error });
}
if (voterData) {
// Voter found
if (voterData.hasRegistered === true) {
return res.status(200).json({
msg: 'Voter already registered',
});
} else {
req.phone = voterData.phone;
req.district = voterData.pinCode;
req._id = voterData._id;
next();
}
} else {
// no data found for given Voter
return res.status(200).json({
msg: 'Invalid VoterID',
});
}
});
};
that code above brings an error but this is how i defined my admin credentials in the .env
ADMIN_EMAIL = bkroland19#gmail.com
ADMIN_PASSWORD =felinho/013
and this is how i defined them in mongodb
{
"email": "bkroland19#gmail.com",
"password": "felinho/013"
}
and this is the resulting error i get yet the email am entering matches those two emails.
Any help please
Am expecting to be allowed to login in when i enter the credentials as they are in the mongodb database
If you store the password in cleartext you don't need bcrypt.compare:
const match = password === adminData.password;
if (match) {
req.adminID = adminData._id;
next();
}
Anyway, it is strongly suggested to encrypt it, you can do it with:
const salt = await bcrypt.genSalt(12);
const encryptedPassword = await bcrypt.hash(password, salt);
const user = await Admin.create({ email, password: encryptedPassword });

Userschema and controller

I am building a user signup and login api and admin signup and login using express and currently I am testing in the postman, but somehow postman keeps return "error": "firstName is not defined" even though I posted firstname etc. here is my code, can anyone help me to explain it what is wrong? I saw so many videos using all different kinds of method, like generateAuthtakoken in the user.model or joi password library, it is just so overwhelming, can you help me to point to a direction as to how to use express to create ?
this is my user.model file:
const mongoose = require("mongoose");
const bcrypt = require("bcrypt");
const validator = require("validator");
const userSchema = new mongoose.Schema(
{
firstName: {
type: String,
required: true,
trim: true,
},
lastName: {
type: String,
required: true,
trim: true,
},
email: {
type: String,
required: true,
trim: true,
unique: true,
},
password: {
type: String,
required: true,
},
role: {
type: String,
enum: ["user", "admin"],
default: "user",
},
contactNumber: { type: String },
profilePicture: { type: String },
},
{ timestamps: true }
);
//static signup method
userSchema.statics.signup = async function (email, password) {
//validation
if (!firstName || !lastName || !email || !password) {
throw Error("All fields must be filled");
}
if (!validator.isEmail(email)) {
throw Error("Email is not valid");
}
if (!validator.isStrongPassword(password)) {
throw Error("Password is not strong enough");
}
const exists = await this.findOne({ email });
if (exists) {
throw Error("Email already in use");
}
const salt = await bcrypt.genSalt(10);
const hash = await bcrypt.hash(password, salt);
const user = await this.create({ email, password: bcrypt.hash });
return user;
};
//static login method
userSchema.statics.login = async function (email, password) {
if (!firstName || !lastName || !email || !password) {
throw Error("All fields must be filled");
}
const user = await this.findOne({ email });
if (!user) {
throw Error("Incorrect Email");
}
const match = await bcrypt.compare(password, user.password);
if (!match) {
throw Error("Incorrect password");
}
return user;
};
module.exports = mongoose.model("User", userSchema);
this is my controller file:
const User = require("../models/user");
const jwt = require("jsonwebtoken");
const createToken = (_id) => {
jwt.sign({ _id }, process.env.JWT_SECRET, { expiresIn: "3d" });
};
//login user
const loginUser = async (req, res) => {
const { email, password } = req.body;
try {
const user = await User.login(email, password);
// create token
const token = createToken(user._id);
res.status(200).json({ email, token });
} catch (error) {
res.status(400).json({ error: error.message });
}
res.json({ msg: "login user" });
};
//signup user
const signupUser = async (req, res) => {
const { email, password } = req.body;
try {
const user = await User.signup(email, password);
// create token
const token = createToken(user._id);
res.status(200).json({ email, token });
} catch (error) {
res.status(400).json({ error: error.message });
}
res.json({ msg: "login user" });
};
module.exports = { signupUser, loginUser };
and my router file:
const express = require("express");
const router = express.Router();
const { signupUser, loginUser } = require("../controller/auth");
//login route
router.post("/login", loginUser);
//signup route
router.post("/signup", signupUser);
module.exports = router;
where exactly do you get this error. Please provide full details to regenerate this error.
But as i could guess
In your static login method you do not need firstName and LastName.
In your signup user method you should be passing those missing required db fields as in your model.

Express-validator not breaking on right chain

I am using express-validator in nodejs. The code is always throwing first error message as a validation result.
validation.js
const { check, validationResult } = require('express-validator')
const {handleError, ErrorHandler} = require('../helper/error');
const resultsOfValidation = (req,res,next) => {
const messages = [];
const errors = validationResult(req);
if(errors.isEmpty()) {
return next(); //pass to controller
}
errors.array().map( err => messages.push(err.msg));
throw new ErrorHandler(400,messages);
}
const createUserValidator = () => {
return [
check('firstName')
.exists({ checkFalsy: true }).withMessage('First Name is mandatory')
.bail()
.isAlpha().withMessage('First Name should have all alphabets')
.bail()
.isLength({min:3}).withMessage('First Name should have minimum 3 characters')
,
check('lastName')
.optional({ checkFalsy: true }) //ignore validation when null or empty
.isAlpha()
.bail()
]
}
module.exports = {
resultsOfValidation,
createUserValidator,
}
user route is below :
const router = express.Router();
router
.get('/',getAllUsers)
.post('/',createUserValidator(),resultsOfValidation,(req,res) => console.log('created'))
.get('/:id',getUserById)
.delete('/',deleteUserById)
module.exports = router;
My user.js controller file is:
module.exports = {
getAllUsers: async (req,res)=> {},
createUser: async (req,res,next)=> {
try{
const errors = resultsOfValidation(req);
if (errors.length >0) {
throw new ErrorHandler(400, errors)
}
const { firstName, lastName } = req.body;
// call createUser from model
const user = await UserModel.createUser(firstName, lastName);
if(!user) {
throw new ErrorHandler(404, 'User not created')
}
// return res.status(200).json({ success: true, user });
} catch (error) {
return res.status(500).json({ success: false, error: error })
}
},
getUserById: async (req,res)=> {},
deleteUserById: async (req,res)=> {},
}
Here is example of post request I made. It should throw First Name should have minimum 3 characters. But I am getting other error First Name is mandatory

TypeError: Cannot read property 'authenticate' of null

I am working in user signin authentication in backend. Whenever I hit send request from postman it shows error as
TypeError: Cannot read property 'authenticate' of null
at /home/saru/mernbootcamp/projbackend/controllers/auth.js
I had check this error in stackoverflow but the solution doesn't match my case
controllers/auth.js
`const User = require("../models/user");
//express-validator
const { check, validationResult } = require('express-validator');
var jwt = require('jsonwebtoken');
var expressJwt = require('express-jwt');
const dotenv = require("dotenv")
const config = dotenv.config({ path: './routes/.env' });
//user object creation for class/model User
const user = new User(req.body);
exports.signin = (req, res) => {
const errors = validationResult(req);
const { email, password } = req.body;
if (!errors.isEmpty()) {
return res.status(422).json({
error: errors.array()[0].msg
});
}
User.findOne({ email }, (err, user) => {
if (err) {
return res.status(400).json({
error: "USER email does not exists"
});
}
console.log(password);
if (!user.authenticate(password)) {
return res.status(401).json({
error: "Email and password do not match"
});
}
//create token
const token = jwt.sign({ _id: user._id }, process.env.SECRET);
//put token in cookie
res.cookie("token", token, { expire: new Date() + 9999 });
//send response to front end
const { _id, name, email, role } = user;
return res.json({ token, user: { _id, name, email, role } });
});
};
`
models/user.js
`var mongoose = require("mongoose");
const crypto = require("crypto");
const uuidv1 = require("uuid/v1");
var userSchema = new mongoose.Schema(
{
email: {
type: String,
trim: true,
required: true,
unique: true
},
encry_password: {
type: String,
required: true
},
salt: String,
},
{ timestamps: true }
);
userSchema
.virtual("password")
.set(function (password) {
this._password = password;
this.salt = uuidv1();
this.encry_password = this.securePassword(password);
})
.get(function () {
return this._password;
});
userSchema.method = {
authenticate: function (plainpassword) {
return this.securePassword(plainpassword) === this.encry_password;
},
securePassword: function (plainpassword) {
if (!password) return "";
try {
return crypto
.createHmac("sha256", this.salt)
.update(plainpassword)
.digest("hex");
} catch (err) {
return "";
}
}
};
module.exports = mongoose.model("User", userSchema);
`
In controllers/auth.js
Replace the if(err) with if(err || !user) in the findOne() method
You can use this code:
User.findOne({email}, (err, user) => {
if (err || !user) {
return res.status(400).json({
error: "USER email does not exists"
})
}
});
Replace if (err) with if (err || !user) and use return before res.status

bcrypt-nodejs compare function always return false

I'm having problem with bcrypt-nodejs' compare function.
The compare function is returning the false value even the password is the right one.
I've tried everything I could and I don't know the what is wrong with my code.
My Folder Structure
src
-config
-config.js
-controller
-AuthenticationController.js
-models
-index.js
-User.js
-policies
-AuthenticationControllerPolicy.js
app.js
routes.js
package.json
I think the problem is with the User.js in models folder.
User.js
const Promise = require('bluebird')
const bcrypt = Promise.promisifyAll(require('bcrypt-nodejs'))
function hashPassword (user, options) {
const SALT_FACTOR = 8
if (!user.changed('password')) {
return
}
return bcrypt
.genSaltAsync(SALT_FACTOR)
.then(salt => bcrypt.hashAsync(user.password, salt, null))
.then(hash => {
user.setDataValue('password', hash)
})
}
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
email: {
type: DataTypes.STRING,
unique: true
},
password: DataTypes.STRING
}, {
hooks: {
beforeCreate: hashPassword,
beforeUpdate: hashPassword,
beforeSave: hashPassword
}
})
User.prototype.comparePassword = function (password) {
return bcrypt.compareAsync(password, this.password)
}
User.associate = function (models) {
}
return User
}
AuthenticationController.js
const {User} = require('../models')
const jwt = require('jsonwebtoken')
const config = require('../config/config')
function jwtSignUser (user) {
const ONE_WEEK = 60 * 60 * 24 * 7
return jwt.sign(user, config.authentication.jwtSecret, {
expiresIn: ONE_WEEK
})
}
module.exports = {
async register (req, res) {
try {
const user = await User.create(req.body)
const userJson = user.toJSON()
res.send({
user: userJson
})
} catch (err) {
res.status(400).send({
error: 'This email account is already in use.'
})
}
},
async login (req, res) {
try {
const {email, password} = req.body
const user = await User.findOne({
where: {
email: email
}
})
console.log('user BEFORE', user)
if (!user) {
console.log('!user')
return res.status(403).send({
error: 'The login information was incorrect'
})
}
console.log('user AFTER', user)
const isPasswordValid = await user.comparePassword(password)
console.log('isPasswordValid BEFORE : ', isPasswordValid)
if (!isPasswordValid) {
console.log('isPasswordValid AFTER : ', isPasswordValid)
return res.status(403).send({
error: 'The login information was incorrect'
})
}
const userJson = user.toJSON()
res.send({
user: userJson,
token: jwtSignUser(userJson)
})
} catch (err) {
res.status(500).send({
error: 'An error has occured trying to log in'
})
}
}
}
route.js
const AuthenticationController = require('./controller/AuthenticationController')
const AuthenticationControllerPolicy = require('./policies/AuthenticationControllerPolicy')
module.exports = (app) => {
app.post('/register',
AuthenticationControllerPolicy.register,
AuthenticationController.register)
app.post('/login',
AuthenticationController.login)
}
You can also check the repo if you want.
GitHubRepo
The usage of bcrypt-nodejs appears to be correct. I would verify that both the password coming in and the hash in the database are what you expect them to be (particularly inside the comparePassword function) to rule out if it's a data issue or not.

Resources