empty value when using statics - node Js - node.js

I am trying to store a user in MongoDB but when I send a test request from postman the req.body successfully logged to the console but MongoDB throw an error of empty values I put a function to test if I got any value in the statistic function but I got nothing back
my schema :
{
profilePicture: {
type: String,
required: true,
},
username: {
type: String,
required: true,
},
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
},
{ timestamps: true }
);
my static function :
UserSchema.statics.signup = async function (
profilePicture,
username,
email,
password
) {
const exist = await this.findOne({ username });
if (exist) {
throw Error("username exist");
}
if (!password || !profilePicture || !username || !email) {
throw Error("missing");
}
// password are empty so hash don't work fix it first
// const salt = await bcrypt.genSalt(10);
// const hash = await bcrypt.hash(password, salt);
const user = await this.create({
username,
password,
profilePicture,
email,
});
return user;
};
const signupUser = async (req, res) => {
const { profilePicture, username, email, password } = req.body;
console.log(req.body);
try {
const user = await User.signup({
profilePicture,
username,
email,
password,
});
const token = createToken(user._id);
res.status(400).json({ username, token });
} catch (error) {
res.status(200).json({ error: error.message });
}
};
when I send a request from postman I got the req.body working but it shows that the fields are empty this function run
if (!password || !profilePicture || !username || !email) {
throw Error("missing");
}
i use body-parser and send data from postman with the same key name

Your static function takes in four parameters, but you are calling it with a single object.
Try changing the User.signup call to:
const user = await User.signup(
profilePicture,
username,
email,
password,
);
(without the { and }).

Related

How to exclude password from user object sent in response in express.js?

I am returning a response object with the user and jwt token whenever a user logs in. But I want to exclude the password field when the user is sent in the response.
Controller:
const loginUser = asyncHandler(async (req, res) => {
const { email, password } = req.body;
// Check for user email
const user = await User.findOne({ email });
if (user && (await bcrypt.compare(password, user.password))) {
res.json({
user: user,
token: generateToken(user._id),
});
} else {
res.status(400);
throw new Error("Invalid credentials");
}
});
if I exclude the password when finding the user like this:
const user = await User.findById(decoded.id).select("-password");
Then bcrypt's compare method will not work as it needs user.password.
Please tell me how I can exclude password from user object in JSON response?
You can set undefined to the user before returning the user.
const loginUser = asyncHandler(async (req, res) => {
const { email, password } = req.body;
// Check for user email
const user = await User.findOne({ email });
if (user && (await bcrypt.compare(password, user.password))) {
user.password = undefined;
res.json({
user: user,
token: generateToken(user._id),
});
} else {
res.status(400);
throw new Error('Invalid credentials');
}
});
Or you can use the toJSON method in the user schema to exclude password:
const userSchema = new mongoose.Schema(
{
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
},
{
toJSON: {
transform(doc, ret) {
delete ret.password;
},
},
},
);

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.

sequelize promise always return false

I'm creating a react-native app.
The flow works like this, a customer has to input an email and password to signup and the data will be saved in the database. Before the data is saved, I've used the pre-hook beforeValidate to hash the password using bcrypt.
Until here, everything worked fine, but I can't seem to return true when the promise from instanceMethod comparePassword is made.
I have a customer model Customer.js file like below:
const Sequelize = require('sequelize');
const bcrypt = require('bcrypt');
const db = require('../config/database');
const Customer = db.define('customer', {
id : {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false
},
email : {
type: Sequelize.STRING,
unique: true,
allowNull: false
},
password : {
type: Sequelize.STRING,
allowNull: false
},
createdAt : {
type: Sequelize.NOW
},
updatedAt : {
type: Sequelize.NOW
}
}, {
hooks: {
afterValidate: (customer) => {
customer.password = bcrypt.hashSync(customer.password, 10);
}
},
instanceMethods: {
comparePassword: (candidatePassword) => {
return new Promise((resolve, reject) => {
bcrypt.compareSync(candidatePassword, this.password, (err, isMatch) => {
if(err) {
return reject(err);
}
if(!isMatch) {
return reject(false);
}
resolve(true);
});
});
}
}
});
module.exports = Customer;
and a snippet of authRoutes.js file like below:
router.post('/login', async (req, res) => {
const { email, password } = req.body;
if ( !email || !password ) {
return res.status(422).send({error: 'Must provide email and password!'});
}
const customer = await Customer.findOne({ where: {email} });
if(!customer) {
return res.status(422).send({error: '1. Invalid email or password!'});
}
try {
await customer.comparePassword(password);
const token = jwt.sign({ email }, 'MY_SECRET_KEY');
res.send({ email, token });
} catch(err) {
return res.status(422).send({error: '2. Invalid email or password!'});
}
});
There's no error or anything but it always catches the "2. invalid email or password" error even tho I've input the correct credentials. Any kind of help is appreciated. Thank you.
I have created a function (comparePassword) to compare password with hashed password Which use bcrypt to compare password.
const bcrypt = require('bcryptjs');
const customer = await Customer.findOne({ where: { email } });
const comparePassword = (hashedPassword, password) => {
return bcrypt.compareSync(password, hashedPassword);
};
try {
if (!comparePassword(customer.password, password) {
return res.status(422).send({ error: '2. Invalid email or password!' });
}
else {
const token = jwt.sign({ email }, 'MY_SECRET_KEY');
return res.status(200).send({ email, token });
}
} catch (err) {
console.log(err)
return res.status(500).send({ error: 'something bad happened on server' });
}
Customer can be defined as a class in Sequelize 4+. Then instance methods can be added as regular class instance methods.
class Customer extends Sequelize.Model {
static table_schema = {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false
},
...
}
static table_options = {
...
}
static init(sequelize){
return super.init(this.table_schema, { this.table_options, ...sequelize })
}
static associate(models) {
}
async comparePassword(candidatePassword){
return bcrypt.compare(candidatePassword, this.password)
}
}
Customer.addHook('afterValidate', async function(customer){
customer.password = await bcrypt.hash(customer.password, 10);
})
Then you should be able to make use of the async comparePassword function in your route, similar to Arya's answer
router.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
if ( !email || !password ) {
return res.status(422).send({error: 'Must provide email and password!'});
}
const customer = await Customer.findOne({ where: {email} });
if (!customer) {
console.log('Failed login [%s] not found', email)
return res.status(422).send({error: 'Invalid email or password!'});
}
const auth = await customer.comparePassword(password);
if (!auth) {
console.log('Failed login [%s] bad password', email)
return res.status(422).send({error: 'Invalid email or password!'});
}
const token = jwt.sign({ email }, 'MY_SECRET_KEY');
res.send({ email, token });
}
catch(err) {
console.error('Failed to process request', err)
return res.status(500).send({error: 'Internal Server Error'});
}
});

How to hide user form data in Request Payload

I am submitting a form by using "POST" method. But, even when I submit the form using "POST" method, I can see the submitted form data in http headers(Request Payload). I'm also using crypto to hash the password. This is the same login system I have deployed to heroku Simple Login System
Request Payload ScreenShot
This is my backend User Model
const mongoose = require('mongoose')
const crypto = require('crypto')
const userSchema = new mongoose.Schema({
username:{
type : String,
max : 32,
trim : true,
required : true,
unique : true,
index : true,
lowercase :true
},
name:{
type : String,
max : 32,
trim : true,
required : true
},
email:{
type : String,
trim : true,
required : true,
unique : true,
lowercase : true
},
profile:{
type : String,
required : true,
},
hashed_password:{
type : String,
required : true,
},
salt : String,
about :{
type : String
},
role :{
type: Number,
default : 0
},
photo:{
data : Buffer,
contentType : String
},
instagram:{
type: String
},
resetPasswordLink : {
data : String,
default : ''
}
},{timestamp : true})
userSchema.virtual('password')
.set(function(password){
//create a temporary variable called _password
this._password = password;
//generate salt
this.salt = this.makeSalt()
//encrypt password
this.hashed_password = this.encryptPassword(password)
})
.get(function(){
return this._password;
})
userSchema.methods = {
authenticate: function(plainText){
return this.encryptPassword(plainText) === this.hashed_password;
},
encryptPassword :function(password){
if(!password) return ''
try{
return crypto.createHmac('sha1',this.salt)
.update(password)
.digest('hex')
}
catch(err) {
return ''
}
},
makeSalt : function(){
return Math.round(new Date().valueOf * Math.random() + '');
}
}
module.exports = mongoose.model('User',userSchema);
Controller Method
const shortId = require('shortid')
const jwt = require('jsonwebtoken')
const expressJwt = require('express-jwt')
exports.signup = (req,res) => {
User.findOne({email: req.body.email}).exec((err,user)=>{
if(user) {
return res.status(400)
.json({ error : 'Email already exist'})
}
const { name, email, password } = req.body
let username = shortId.generate()
let profile = `${process.env.CLIENT_URL}/profile/${username}`
let newUser = new User( { name, email , password, profile, username })
newUser.save((err,success)=>{
if(err){
return res.status(400)
.json({error: err})
}
// res.json({
// user:success
// })
res.json({ message: 'Signup Success ! Please return to the Login page' })
})
})
};
exports.signin = (req,res) =>{
const { email,password } = req.body;
// check if user exists
User.findOne({ email }).exec((err,user)=> {
if(err || !user ){
return res.status(400).json({
error : 'User with that email does not exist,Please Sign Up'
});
}
// user authentication
if(!user.authenticate(password)) {
return res.status(400).json({
error : 'Email and password do not match'
});
}
// generate a token and send to client
const token = jwt.sign({ _id: user._id},process.env.JWT_SECRET, {expiresIn : '1d'} )
res.cookie('token',token, { expiresIn:'2d' })
const { _id, username, name, email, role } = user;
return res.json({
token,
user : { _id, username, name, email, role }
})
});
}
exports.signout = (req,res)=> {
res.clearCookie("token")
res.json({ message: 'Successfully Signed Out' })
}

How to make password validation in NodeJS with Mongoose

I have registration form with username, mail, password and password2. I want to verify passwords that they actually match. I verify practically everything in Mongoose Scheme but I cannot find any useful information in documentation how to grab password2 without actually saving it to database. (I have function to crypt password which runs only before saving)
const userSchema = new mongoose.Schema({
username: {
type: String,
unique: true,
required: true,
trim: true,
validate(value) {
if (!validator.isAlphanumeric(value , 'pl-PL')) {
throw new Error('Name cannot contain special characters.')
}
}
},
email: {
type: String,
unique: true,
required: true,
trim: true,
lowercase: true,
validate(value) {
if (!validator.isEmail(value)) {
throw new Error('Email is invalid')
}
}
},
password: {
type: String,
required: true,
validate(value) {
console.log(value)
if(value !== this.password2) {
throw new Error("Passwords don't match. Try again.")
}
if(value.length < 8) {
throw new Error("Passwords is too short. At least 8 characters.")
}
}
},
tokens: [{
token: {
type: String,
required: true
}
}]
})
You don't need to make password2 a part of userSchema. The better way is to make a compare password function like this:
UserSchema.methods.comparePassword = function(plaintext, callback) {
return callback(null, Bcrypt.compareSync(plaintext, this.password));
};
also you can make a use of Schema.pre:
UserSchema.pre("save", function(next) {
if(!this.isModified("password")) {
return next();
}
this.password = Bcrypt.hashSync(this.password, 10);
next();
});
After this, you need to call the compare function from user controller. Something like this (depending on your logic):
var user = await UserModel.findOne({ username: request.body.username }).exec();
if(!user) {
return response.status(400).send({ message: "The username does not exist" });
}
user.comparePassword(request.body.password, (error, match) => {
if(!match) {
return response.status(400).send({ message: "The password is invalid" });
}
});
For details you can read this excellent article.
You can check password and password2 in your register route, and if they are same you can continue to register.
A sample register route would be like this:
router.post("/register", async (req, res) => {
try {
const { username, email, password, password2 } = req.body;
if (password !== password2) return res.status(400).send("Passwords dont match");
let user = await User.findOne({ email });
//or
//let user = await User.findOne({ username });
if (user) return res.status(400).send("User already registered.");
user = new User({ username, email, password });
user = await user.save();
//todo: at this point you may generate a token, and send to the client in response header or body
res.send(user);
} catch (err) {
console.log(err);
res.status(500).send("Server error");
}
});

Resources