The problem start when I use the bcrypt middleware to encrypt my password.
Whitout bcrypt I could save the users, but with it not now.
My users.js file
'use strict'
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const bcrypt = require('bcrypt')
const UserSchema = new Schema({
email: { type: String, unique: true, lowercase: true },
displayName: String,
password: { type: String, select: false }
})
UserSchema.pre('save', (next) => {
let user = this
if (!user.isModified('password')) {
return next();
}
bcrypt.genSalt(10, (err, salt) => {
if (err) return next(err)
bcrypt.hash(user.password, salt, null, (err, hash) => {
if (err) return next(err)
user.password = hash
next();
})
})
})
module.exports = mongoose.model('User', UserSchema)
My router.js file:
const express = require('express')
const router = express.Router()
const mongoose = require('mongoose')
const User = require('./model/user')
const bcrypt = require('bcrypt')
router.post('/user', (req, res) => {
console.log(req.body)
let user = new User()
user.email = req.body.email
user.displayName = req.body.displayName
user.password = req.body.password
user.save((err, stored) => {
res.status(200).send({
user: stored
})
})
})
This is the server response:
{}
My db is not affected...
I can see two mistakes in the provided code:
1. this in the pre-save middleware is not a user document instance
Arrow functions do not provide their own this binding:
In arrow functions, this retains the value of the enclosing lexical context's this. [source]
Change your code to the following:
UserSchema.pre('save', function (next) {
const user = this;
// ... code omitted
});
2. Not handling duplicate key MongoError
The request might fail with MongoError: E11000 duplicate key error collection since email field is unique. You are ignoring such fail and since stored in your user.save() is undefined the response from the server is going to be an empty object.
To fix this issue you need to add a handler in the following code:
user.save((err, stored) => {
if (err) {
throw err; // some handling
}
res.status(200).send({user: stored});
});
Related
I'm really new to NodeJs and MongoDB or web development in general. I'm following a tutorial on how to make a registration system that was posted about 2 years ago. With these codes below, he was able to send a post request test using postman and his data was saved into MongoDB, however, when I try to send a post request on postman, it keeps loading at "sending request" and data was never saved to mongoDB...I'm not sure if nodejs has changed syntax or if i'm doing something wrong... please help!!
this is the code for user.controller.js
const mongoose = require('mongoose');
const User = mongoose.model('User');
module.exports.register = (req, res, next) => {
var user = new User();
user.fullName = req.body.fullName;
user.email = req.body.email;
user.password = req.body.password;
user.save((err, doc) => {
if (!err)
res.send(doc);
else {
if (err.code == 11000)
res.status(422).send(['Duplicate email adrress found.']);
else
return next(err);
}
});
this is the code for user.model.js:
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
var userSchema = new mongoose.Schema({
fullName: {
type: String
},
email: {
type: String
},
password: {
type: String
},
saltSecret: String
});
// Events
userSchema.pre('save', function (next) {
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(this.password, salt, (err, hash) => {
this.password = hash;
this.saltSecret = salt;
next();
});
});
});
mongoose.model('User', userSchema);
this is the code for server(app.js)
const MongoClient = require('mongodb').MongoClient;
const uri = process.env.MONGODB_URI;
const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true });
client.connect(err => {
const collection = client.db("test").collection("devices");
// perform actions on the collection object
console.log(`MONGODB CONNECTION SUCCEEDED`);
client.close();
});
require('./user.model');
In controller you have mongoose to write data to mongo but in your server file you are connecting to mongodb using native mongo driver. Hence, it won't work. Either both places you need to have mongodb native driver or mongoose.
Use below code where I have modified the server start file to use mongoose.
const mongoose = require('mongoose'),
const m_url = 'mongodb://127.0.0.1:27017/',
db_name = 'test', // use your db name
m_options = {
'auto_reconnect': true,
useNewUrlParser: true,
useCreateIndex: true,
useUnifiedTopology: true
}
mongoose.connect(m_url + db_name, m_options, function (err) {
if (err) {
console.log('Mongo Error ' + err);
} else {
status.mongo = 'Running'
console.log('MongoDB Connection Established');
}
});
// import/require user controller.
So, here's my user schema where i declared hello method onto the Userschema which im using to test
//user.model.js
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const UserSchema = new Schema({
username: {
type: String,
required: true,
unique: true,
trim: true,
minlength: 3
},
password: { type: String, required: true }
});
UserSchema.methods.hello = () => {
console.log("hello from method");
};
const User = mongoose.model("User", UserSchema);
module.exports = User;
here's routes file
//authroutes.js
const router = require("express").Router();
let User = require("../models/user.model");
router.route("/").get((req, res) => {
res.send("auth route");
});
router.route("/signup").post((req, res) => {
const username = req.body.username;
const password = req.body.password;
const newUser = new User({
username,
password
});
newUser
.save()
.then(() => res.json(`${username} added`))
.catch(err => console.log(err));
});
router.route("/login").post(async (req, res) => {
await User.find({ username: req.body.username }, function(err, user) {
if (err) throw err;
//this doesnt work
user.hello();
res.end();
});
});
module.exports = router;
in the login route im calling hello function to test but that doesnt work and throws this error
TypeError: user.hello is not a function
You need to use User.findOne instead of User.find, because find returns an array, but what we need is an instance of the model.
Also Instance methods shouldn't be declared using ES6 arrow functions.
Arrow functions explicitly prevent binding this, so your method will not have access to the document and it will not work.
So you had better to update method like this:
UserSchema.methods.hello = function() {
console.log("hello from method");
};
I've come across a blocker on attempting to promisify a mongoose method.
As far as my understanding goes I should be able to promisify fn's that take in callbacks with an error and a parameter, but in this case I get this error:
TypeError: this.Query is not a constructor
model code:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
module.exports = mongoose.model('User', new Schema({
name: String,
password: String,
admin: Boolean
}));
breaking code:
var User = require('./app/models/user');
var { promisify } = require('util');
var findUserPromise = promisify(User.findOne);
...
findUserPromise({ name: req.body.name })
.then(user => console.log("do something with the user"))
.catch(err => { console.log("err ", err) });
Any help is very much appreciated!
mongoose is already very promise-friendly. To get a promise from findOne(), you just need to call .exec():
Instead of
var findUserPromise = promisify(User.findOne);
...
findUserPromise({ name: req.body.name })
.then(user =>
...
Just call it with .exec()
:
User.findOne({ name: req.body.name }).exec()
.then(user =>
...
Yes as #Jim B answer, mongoose is promise friendly. you can also use async and await
const User = require('./app/models/user');
module.export = {
userDetails: async (req, res, next) => {
try {
const user = await User.findOne({ name: req.body.name });
console.log(user);
}
catch(err) {
console.log(err);
}
}
}
Here my authentication process:
Session > Controller.js
const jwt = require("jsonwebtoken");
const repository = require("./repository");
const config = require("../../config");
const logger = require("../../utilities/logger");
exports.login = async (req, res) => {
if (!req.body.password || !req.body.username) {
res.preconditionFailed("Credentials required");
return;
}
try {
const user = await repository.findUser(req.body);
if (!user || !user.comparePasswords(req.body.password)) {
res.json({ success: false, message: "Authentication failed." });
return;
}
const token = jwt.sign(user.toObject(), config.secret, { expiresIn: 1440 });
logger.info("User loged in with success. Login token", token);
res.json({
success: true,
token,
});
} catch (err) {
res.send(err);
}
};
User > Model.js
const mongoose = require("mongoose");
const bcrypt = require("bcrypt");
const SALT_WORK_FACTOR = 10;
const Schema = mongoose.Schema;
const userSchema = new Schema({
id: { type: String, required: true },
username: { type: String, required: true },
password: { type: String, required: true },
}, {
timestamps: true,
});
userSchema.pre('save', function(next) {
var user = this;
// only hash the password if it has been modified (or is new)
if (!user.isModified('password')) return next();
// generate a salt
bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) {
if (err) return next(err);
// hash the password using our new salt
bcrypt.hash(user.password, salt, function(err, hash) {
if (err) return next(err);
// override the cleartext password with the hashed one
user.password = hash;
next();
});
});
});
userSchema.methods.comparePasswords = function(candidatePassword) {
return bcrypt.compareSync(candidatePassword, this.password);
};
module.exports = mongoose.model("User", userSchema);
In the user model, I'm using the bcrypt method compareSync. It's recommended to use the async method compare (https://www.npmjs.com/package/bcrypt).
Could someone explain why? And what would be a good implementation? It's working well like that but I would like to be sure I'm using the best practices.
compareSync is extremely CPU intensive task. Since Node.js Event Loop is single threaded, you block entire application unless you comparison got finished.
Main rule using node.js is to always avoid synchronous code execution.
Reason is event-driven nature of framework.
Good & detailed explanation can be found here
MongoDB inserts document without properties(look at the bottom of the post to see how my collection looks like).I'm using postman to test my db when I try to insert the following:
{
"username":"JohnDoe",
"password" : "123456"
}
I'm building a MEAN stack application.Also, I noticed if I set the properties to be required, postman tells me that it Failed to register user. That's why I comment it out, but even without it and getting a postive response I still get empty documents in my collection.
Postman tells me:
{
"success": true,
"msg": "User registered"
}
My user.js file
const bcrypt = require ('bcryptjs');
const config = require ('../config/database');
//UserSchema
const UserSchema = mongoose.Schema({
username: {
type: String,
//required: true
},
password: {
type: String,
//required: true
}
});
const User = module.exports = mongoose.model("User", UserSchema);
//To user function outside
module.exports.getUserById = function(id, callback){
User.findById(id,callback);
}
module.exports.getUserByUsername= function(username, callback){
const query = {username: username}
User.findOne (query,callback);
}
User.addUser = function (newUser, callback) {
bcrypt.genSalt(10, (err, salt) =>{
bcrypt.hash(newUser.password, salt, (err, hash) => {
newUser.password = hash;
newUser.save(callback);
});
});
}
My users.js file:
const express = require('express');
const router = express.Router();
const passport = require('passport');
const jwt = require('jsonwebtoken');
const User = require('../modules/user');
// Register
router.post('/register', (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:'Failed to register user'});
} else {
res.json({success: true, msg:'User registered'});
}
});
});
module.exports = router;
What I see in my collection:
{
"_id": {
"$oid": "5937b36bafdd733088cb27d0"
},
"__v": 0
}
You should learn about what is mongoose statics and methods.
In User model you should be declaring the functions as methods and statics based on the way you want it.
const bcrypt = require ('bcryptjs');
const config = require ('../config/database');
//UserSchema
const UserSchema = mongoose.Schema({
username: {
type: String,
//required: true
},
password: {
type: String,
//required: true
}
});
//To user function outside
UserSchema.statics.getUserById = function(id, callback){
User.findById(id,callback);
}
UserSchema.statics.getUserByUsername= function(username, callback){
const query = {username: username}
User.findOne (query,callback);
}
UserSchema.methods.addUser = function (callback) {
bcrypt.genSalt(10, (err, salt) =>{
bcrypt.hash(newUser.password, salt, (err, hash) => {
this.password = hash;
this.save(callback);
});
});
}
exports.User = mongoose.model("User", UserSchema);
In your controller user file, you should use addUser with your instance of the User model not on the Model you exporting. Check below..
const express = require('express');
const router = express.Router();
const passport = require('passport');
const jwt = require('jsonwebtoken');
const User = require('../modules/user');
// Register
router.post('/register', (req, res, next) => {
let newUser = new User({
username: req.body.username,
password: req.body.password
});
newUser.addUser(function(err, user) => {
if(err){
res.json({success: false, msg:'Failed to register user'});
} else {
res.json({success: true, msg:'User registered'});
}
});
});
module.exports = router;