I am building a Node, Express-based API for user authentication. I am using mongo to store the data. Using postman, I submitted a /post request by passing in the following object
{
"username": "abc",
"password": "abc123",
"email": "abc#ghi.com"
}
under req.body.
This is how my /post function looks in the code:
//create one user
router.post('/createUser', async (req, res) => {
if (!req.body.email || !req.body.password || !req.body.username) {
return res.status(400).send({
message: "No username/password/email specified"
});
}
const newUser = new User({
email: req.body.email,
username: req.body.username,
password: req.body.password
});
await User.create(newUser, (err, data) => {
//res.send(data);
if(err) {
console.log(err);
res.status(500).send('Error creating user');
}
});
});
User.create() method calls .save() method under the covers. I have a pre-condition on saving to encrypt passwords. On running the post, I get an error that says UnhandledPromiseRejectionWarning: Error: data and salt arguments required
I did some console logging and noticed that this is happening because user.password is coming in as undefined. So it looks like my request is not going through properly from the postman.
Edit:
Here is the schema:
const userSchema = new mongoose.Schema({
id: {
type: Number
},
email: {
type: String,
unique: true,
required: true,
},
username: {
type: String,
unique: true,
required: true
},
password: {
type: String,
required: true
},
});
userSchema.pre('save', (next) => {
const user = this;
console.log(user.password);
bcrypt.hash(user.password, 10, (err, hash) => {
if (err) {
next(err);
} else {
user.password = hash;
next();
}
});
});
Can someone please help me understand what's wrong?
You cannot use arrow function in .pre hooks because arrow function does not bind "this". "this" is supposed to refer to each individual user that about to be saved. however if you use "this" inside the arrow function, it will point to the global object. run this code console.log(this) you will see. use arrow functions for standalone functions. in your case, you are creating a method that part of the object so you should avoid using arrow function
I do not use .pre, because some mongoose queries bypass mongoose middleware, so u need to do extra work. so instead I hash the password inside the router, so everything related to user will be in the same place. single source of truth
const bcrypt = require("bcrypt");
router.post('/createUser', async (req, res) => {
if (!req.body.email || !req.body.password || !req.body.username) {
return res.status(400).send({
message: "No username/password/email specified"
});
}
const newUser = new User({
email: req.body.email,
username: req.body.username,
password: req.body.password
});
//we created newUser and now we have to hash the password
const salt = await bcrypt.genSalt(10);
newUser.password = await bcrypt.hash(newUser.password, salt);
await newUser.save();
res.status(201).send(newUser)
//201 code success for something is created
});
here is the list of http status codes:
https://httpstatuses.com/
The password from postman is getting received on your NodeJS code.
In your code:
const newUser = new User({
email: req.body.email,
username: req.body.username,
password: req.body.password
});
when you do this, your expected output from newUser changes.
so when your code reaches here...
const user = this;
console.log(user.password);
Instead of logging user.password
try logging user itself like...
console.log(user)
and see if
"(const user=this)" is giving what you expected.
signUp: (req, res, next) => {
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(req.body.password, salt, (err, hashedPass) => {
let insertquery = {
'_id': new mongoose.Types.ObjectId(),
'username': req.body.username,
'email': req.body.password,
'salt': salt,
'password': hashedPass
};
user.create(insertquery, function (err, item) {
});
});
});
}
Related
I'm working with routes on node js. I created a user model shown below -
const mongoose = require("mongoose");
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
const validator = require("validator");
require("dotenv").config();
const userSchema = mongoose.Schema(
{
email: {
type: String,
required: true,
unique: true,
trim: true,
lowercase: true,
validate(value) {
if (!validator.isEmail) {
throw new Error("Invalid Email");
}
},
},
password: {
type: String,
required: true,
trim: true,
},
role: {
type: String,
enum: ["user", "admin"],
default: "user",
},
name: {
type: String,
required: true,
maxlength: 21,
},
phone: {
required: true,
type: Number,
maxlength: 12,
},
},
{ timestamps: true },
);
userSchema.pre("save", async function (next) {
if (user.isModified("password")) {
// hash the password
const salt = await bcrypt.genSalt(10);
const hash = await bcrypt.hash(this.password, salt);
this.password = hash;
}
next();
});
const User = mongoose.model("User", userSchema);
module.exports = {
User,
};
And then I created a file containing user routes shown below -
const express = require("express");
const router = express.Router();
require("dotenv").config();
const { User } = require("../../models/userModel");
router.route("/signup").post(async (req, res) => {
// const { email, password, name, phone } = req.body;
console.log(req.body);
// try {
// // Check if user email exists
// // create user instance and hash password
// const user = new User({
// email: req.body.email,
// password: req.body.password,
// name: req.body.name,
// phone: req.body.phone,
// });
// // generate jwt token
// console.log("user is saving");
// const userDoc = await user.save();
// // send email
// // save....send token with cookie
// res
// .cookie("access-token", "jflsakjusdilfjadslfj32j43lrf")
// .status(200)
// .send(userDoc);
// } catch (error) {
// res
// .status(400)
// .json({ message: "Error while creating user", error: error });
// }
const user = new User({
name: req.body.name,
email: req.body.email,
password: req.body.password,
phone: req.body.phone,
});
user
.save()
.then((doc) => {
console.log("User saved");
res.send(doc);
})
.catch((err) => {
console.log(err);
});
});
module.exports = router;
But don't know why I'm getting this error -
ReferenceError: user is not defined
at model.<anonymous> (D:\CovidHelpers\CovidHelpers\server\models\userModel.js:46:3)
at callMiddlewareFunction (D:\CovidHelpers\CovidHelpers\node_modules\kareem\index.js:483:23)
at model.next (D:\CovidHelpers\CovidHelpers\node_modules\kareem\index.js:58:7)
at _next (D:\CovidHelpers\CovidHelpers\node_modules\kareem\index.js:107:10)
at D:\CovidHelpers\CovidHelpers\node_modules\kareem\index.js:508:38
at processTicksAndRejections (internal/process/task_queues.js:75:11)
I have just created a new project in mongodb, gave database and network access and it's connecting successfully but also getting this error
I have done this before also and it was working fine but don't know why am I getting this now :(
Any help is appreciated
save is document middleware and in document middleware functions, this refers to the document. So in your case, I believe it should be this.isModified("password") instead of user.isModified("password").
You can delete userSchema.pre() middleware and transfer the password hashing logic inside the router. Also you can simplify your router code like this:
router.route("/signup").post(async (req, res) => {
try {
const salt = await bcrypt.genSalt(10);
const hash = await bcrypt.hash(req.body.password, salt);
req.body.password = hash;
let user = await User.create(req.body)
res.status(200).json(user)
} catch (error) {
res.status(400).json({ error: error });
}
});
RECOMMENDATION:
I would recommend you to try the great Mongoose plugin called passport-local-mongoose that will do this for you out of the box, and it will also give you some nice authentication features if you are using passport for authentication.
Package: https://www.npmjs.com/package/passport-local-mongoose
You don't actually get access to the document, in the mongoose's pre('save') hook.
For your usecase, you can do the hasing before you save the user.
I am trying to get mongoose return data from local MongoDB instance. Eveything else is running fine. The form is giving data to passport. I have also inserted data into mongodb manually. The only error is in user.findOne. When I print user into console, instead of returning some data it returns null
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const User = require('../models/member');
// authentication using passport
passport.use(new LocalStrategy({
usernameField: 'user',
passwordField: 'password'
},
function (username, password, done) {
User.findOne({ user: username }, function (err, user) {
// console.log(user, username, password);
if (err) {
console.log(`Error in configuring passport-local \n ${err}`);
return done(err);
}
if (!user) {
console.log(`Invalid username or password!!`)
return done(null, false);
}
return done(null, user);
});
}
));
My schema:
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
user: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
dept: String,
yr: Number,
name: {
type: String
}
}, {
timestamps: true
});
module.exports = mongoose.model('member_list', userSchema, 'member_list');
Here's my full code: https://github.com/krush11/site
Let me know if you need any more info
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const User = require('../models/member');
// authentication using passport
passport.use(new LocalStrategy({
usernameField: 'user',
passwordField: 'password'
},
async function (username, password, done) {
await User.findOne({ user: username }, function (err, user) {
// console.log(user, username, password);
if (err) {
console.log(`Error in configuring passport-local \n ${err}`);
return done(err);
}
if (!user) {
console.log(`Invalid username or password!!`)
return done(null, false);
}
return done(null, user);
});
}
));
The function looks good but try making it asynchronous then await the query from the database. Take a look at the modification I made to your code. You might also want to refactor your code and use promises instead of callback and wrap everything inside of try{} catch{} block.
But making the function asynchronous and awaiting the query will fix the issue, this is because the findOne() method actually returns a query and might not have completed before you console it.
Let me know if you still have any issue.
I have a simple ExpressJS/Node backend that contains a MongoDB database for which I use mongoose to interact. I can add objects to the db based on the UserSchema:
const userSchema = mongoose.Schema({
email : {
type: String,
required: true,
trim: true,
unique: 1
},
password : {
type: String,
required: true,
minlength: 5
},
name : {
type: String,
required: true,
maxlength: 30
},
lastname : {
type: String,
required: true,
maxlength: 30
},
cart : {
type : Array,
default: []
},
history : {
type: Array,
default: []
},
role : {
type: Number,
default : 0
},
token : {
type: String
}
});
From the express Server, I can register and add a new user to the DB and I know this works
Server.js
//========================================
// Register User
//========================================
app.post('/api/users/register', (req, res) => {
//create new User
const user = new User(req.body);
//save user
user.save((err, doc) => {
if(err)
return res.json({success: false, err});
res.status(200).json({
success : true,
userdata: doc
});
});
})
In User.js
//========================================
// SAVE in DB
//========================================
const User = mongoose.model('User', userSchema);
Now when I want to login, operation where I need to check the email and password match I encounter a problem when everything is fine and I want to add the JWT to the object all is good until it gets to the save method, there nothing happens and it doesn't respond anymore. It's like it goes in an infinite loop. I get error when something is wrong, but on the positive case, it disappears and sends no response, to either mongo, node, debug anything.
Server.js
app.post('/api/users/login', (req, res) => {
//find the email for the user
User.findOne({'email' : req.body.email} , (err, user) =>{
if(!user)
return res.json({loginSuccess : false, message : 'Authentication failed, email not found'});
//check the password
user.comparePassword(req.body.password, (error, isMatch) => {
if(!isMatch)
return res.json({loginSuccess : false, message : 'Wrong password'});
//generate token
user.generateToken((err, user) => {
if(err)
return res.status(400).send(err);
//store token as a cookie
res.cookie('w_auth', user.token).status(200).json({
loginSuccess : true
})
})
})
})
})
User.js
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const SALT_I = 10;
require('dotenv').config();
//========================================
// User Login
//========================================
userSchema.methods.comparePassword = function (candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.password, function(error, isMatch){
if(error)
return cb(error);
cb(null, isMatch);
})
}
userSchema.methods.generateToken = function (cb) {
var user = this;
var token = jwt.sign(user._id.toHexString(),process.env.SECRET)
user.token = token;
user.markModified('anything');
user.save(function(err,user){
if(err) return cb(err);
cb(null,user);
})
}
I get no more feedback in node console, debug, Mongo or even Postmen(I can wait here for minutes ) after user.save(...). I know it gets the good user and everything but I don't really know where to get from here. Also in Mongo I see no field for the token, I initially add an object with no token, can this affect everything? Is there another procedure to update an existing object in the collection?
In case GitHub is needed to see the code: Link
Indeed it's really strange, couldn't really debug what's wrong with this 'save' method. As a workaround, however, this one seems to work fine:
userSchema.methods.generateToken = function (cb) {
var user = this;
var token = jwt.sign(user._id.toHexString(), "mystupidsecret");
console.log("in generateToken");
console.log(user);
user.token = token;
console.log(user.token);
var email = user.email;
//save token
User.updateOne({ _id: user._id }, { $set: { token: token } }, function(err, user){
if(err) {
console.log(err);
return cb(err);
}
cb(null, user);
// this one is for debug only!
User.findOne({'email' : email} , (err, user) =>{
console.log("After update: ", user)
});
});
console.log('done');
}
It yields the following:
After update: { cart: [],
history: [],
role: 0,
_id: 5f3e48f09c7edc3f1c24a860,
email: 'abc233#wp.pl',
password:
'$2b$10$iDeeehLOzbQi3dawqW8Lg.HPOvcRBDIS/YD9D1EmqBOH9Be31WpX2',
name: 'ABCDEFGH',
lastname: 'Doeasdasdas',
__v: 0,
token:
'eyJhbGciOiJIUzI1NiJ9.NWYzZTQ4ZjA5YzdlZGMzZjFjMjRhODYw.aH9tCMbIK9t3CReiQg3Azln9Ca8xS7W0xL3qCMOKniY' }
The empty object comes to login. The use of registration, done in the similarity of the login and so everything works. By sending a request through Postman, you can register a user and check whether such one exists in the database. When you send a request for a login, instead of a token, a message comes from the last block 'else' “User with such email address not found”.
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const keys = require('../config/keys');
module.exports.login = async function (req, res) {
console.log('req.body', req.body); //Empty object {}
const candidate = await User.findOne({
email: req.body.email
});
if (candidate) {
const passwordResult = bcrypt.compareSync(req.body.password,
candidate.password);
if (passwordResult) {
const token = jwt.sign({
email: candidate.email,
userId: candidate._id
}, keys.jwt, {expiresIn: 60 * 60});
res.status(200).json({
token: `Bearer ${token}`
})
} else {
res.status(401).json({
message: 'Passwords do not match'
})
}
} else {
console.log(req.body.email);
console.log(candidate);
res.status(404).json({
message: 'User with such email address not found'
})
}
};
module.exports.register = async function (req, res) {
console.log('req.body', req.body);
const candidate = await User.findOne({
email: req.body.email
});
if (candidate) {
res.status(409).json({
message: "User with this email address already exists"
})
} else {
const salt = bcrypt.genSaltSync(10);
const password = req.body.password;
const user = new User({
email: req.body.email,
password: bcrypt.hashSync(password, salt)
});
try {
await user.save();
res.status(201).json(user)
} catch (e) {
}
}
};
! [Registration works correctly] (https://imgur.com/a/9T5vRMD)
! [Login does not work correctly] (https://imgur.com/a/rQOiw2w) "Must be token, because this user is already there"
I found the answer myself. I use " x-form-urlencoded", the login works correctly and I get a valid token. Apparently the problem is in the internal implementation of the Postman, because the data entered with the help of "rav" should also be valid.
I am using Satellizer in my MEAN Stack webapp to login users. The satellizer module uses JSON Web Tokens.
The token is created in:
var jwt = require('jwt-simple');
function createJWT(user) {
var payload = {
sub: user._id,
user: {
displayName: user.displayName,
email: user.email,
admin: user.admin
},
iat: moment().unix(),
exp: moment().add(2, 'hours').unix()
};
return jwt.encode(payload, config.TOKEN_SECRET);
}
app.post('/auth/login', function(req, res) {
User.findOne({ email: req.body.email }, '+password', function(err, user) {
if (!user) {
return res.status(401).send({ message: 'Wrong email and/or password' });
}
user.comparePassword(req.body.password, function(err, isMatch) {
if (!isMatch) {
return res.status(401).send({ message: 'Wrong email and/or password' });
}
res.send({ token: createJWT(user) });
});
});
});
The thing is that later in a function, I need to update the user key inside the payload object.
Is this possible?
Basically token looks like string. when you change payload then your token is changed (new string). You can't change token / payload without changing string. You can create new one based on previous.
Remember to return new token to client application.