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;
Related
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});
});
I used Passport to create login.
However, I get:
TypeError: Cannot read property 'findOne' of undefined
when trying to login.
After debugging, I confirmed that user was undefined after async of localStrategy.js.
I think all the actions are perfect, but there is a problem with my code.
Can I tell you where I made the mistake?
passport/index.js
const local = require('./localStrategy');
const { User } = require('../model/user');
module.exports = (passport) => {
passport.serializeUser((user, done) => {
done(null, user._id);
});
passport.deserializeUser((id, done) => {
User.findOne({ id: id })
.then((user) => done(null, user))
.catch((err) => done(err));
});
local(passport);
};
passport/localStrategy.js
const LocalStrategy = require('passport-local').Strategy;
const { User } = require('../model/user');
module.exports = (passport) => {
passport.use(
new LocalStrategy(
{
usernameField: 'email',
passwordField: 'password'
},
async (email, password, done) => {
try {
const exUser = await User.findOne({ email: email });
if (exUser) {
const result = await bcrypt.compare(password, exUser.password);
if (result) {
done(null, exUser);
} else {
done(null, false, { message: 'failed.' });
}
} else {
done(null, false, { message: 'failed.' });
}
} catch (err) {
console.error(err);
done(err);
}
}
)
);
};
routes/user.js
const express = require('express');
const router = express.Router();
const { isLoggedIn, isNotLoggedIn } = require('./middleware');
const usersController = require('../controller/users_controller');
router.get('/login', isNotLoggedIn, usersController.user_login);
controller/users_controller.js
const User = require('../model/user');
const bcrypt = require('bcrypt');
const passport = require('passport');
exports.user_login = (req, res, next) => {
passport.authenticate('local', (authError, user, info) => {
if (authError) {
console.error(authError);
return next(authError);
}
if (!user) {
return res.send("not user.")
}
return req.login(user, (loginError) => {
if (loginError) {
console.error(loginError);
return next(loginError);
}
return res.send("login successfully")
});
})(req, res, next);
};
model/user.js
const mongoose = require('mongoose');
let UserSchema = new mongoose.Schema({
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
require: true
},
createAt: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('User', UserSchema);
const { User } = require('../model/user');
This code return User = undefined
Can you show code in '../model/user'?
Are you export with:
module.exports = {
User: your_user
}
?
If you use:
module.exports = User;
you should call
const User = require('../model/user');
instead of:
const { User } = require('../model/user');
or you can change export in '../model/user':
module.exports = {
User: your_user
}
It should be _id not id in your query
passport/index.js
User.findOne({ _id: id })
.then((user) => done(null, user))
.catch((err) => done(err));
});
In passport/localStrategy file correct import of Model
const User = require('../model/user');
Trying to wrap my head around the following issue for couple days now.
I have a web app, that takes login, afterwards, the user can create a list of students. I am trying to make the list being visible to the user who posted it ONLY.
Below is my work so far.
students.controller.js
const express = require('express');
var router = express.Router();
var bodyParser = require('body-parser')
const mongoose = require('mongoose');
const passport = require('passport');
const Student = require('../models/student');
const passportConfig = require('../config/passport');
const app = express();
router.get('/',passportConfig.isAuthenticated,(req, res) => {
res.render('students');
});
router.post('/',(req, res) => {
InsertRecord(req, res);
});
function InsertRecord(req, res){
var student = new Student();
student.fullName = req.body.fullname;
student.phone = req.body.phone;
student.save((err, doc) => {
if (!err)
res.redirect('students/list');
else {
console.log(' Error during insertion: '+ err);
}
});
}
router.get('/list',passportConfig.isAuthenticated, (req, res) => {
Student.find((err, docs) => {
if (!err) {
res.render('list', {
list:docs
});
}
else {
console.log('Error in retrieving students: '+ err);
}
});
});
module.exports = router;
Model Schema Student.js
const mongoose = require('mongoose');
var ObjectId = mongoose.Schema.Types.ObjectId;
const studentSchema = new mongoose.Schema({
fullName: {
type: String
},
phone: {
type: Number
},
author: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
}
});
const Student = mongoose.model('Student', studentSchema);
module.exports = Student;
User.js Schema
const bcrypt = require('bcrypt');
const crypto = require('crypto');
const mongoose = require('mongoose');
const Student = require('../models/student');
const userSchema = new mongoose.Schema({
email: { type: String, unique: true },
password: String,
passwordResetToken: String,
passwordResetExpires: Date,
author: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
}
},
snapchat: String,
facebook: String,
twitter: String,
google: String,
github: String,
instagram: String,
linkedin: String,
steam: String,
tokens: Array,
profile: {
name: String,
gender: String,
location: String,
website: String,
picture: String
}
}, { timestamps: true });
/**
* Password hash middleware.
*/
userSchema.pre('save', function save(next) {
const user = this;
if (!user.isModified('password')) { return next(); }
bcrypt.genSalt(10, (err, salt) => {
if (err) { return next(err); }
bcrypt.hash(user.password, salt, (err, hash) => {
if (err) { return next(err); }
user.password = hash;
next();
});
});
});
/**
* Helper method for validating user's password.
*/
userSchema.methods.comparePassword = function comparePassword(candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.password, (err, isMatch) => {
cb(err, isMatch);
});
};
/**
* Helper method for getting user's gravatar.
*/
userSchema.methods.gravatar = function gravatar(size) {
if (!size) {
size = 200;
}
if (!this.email) {
return `https://gravatar.com/avatar/?s=${size}&d=retro`;
}
const md5 = crypto.createHash('md5').update(this.email).digest('hex');
return `https://gravatar.com/avatar/${md5}?s=${size}&d=retro`;
};
const User = mongoose.model('User', userSchema);
module.exports = User;
Please let me know if anything else is needed.
How about using JWT?
JWT is token-based Authorization way. You can store user's email or _id in jwt.
When user logged in, the server provide jwt that store user's email. When user requested the list with jwt, you can find the student with user's _id in jwt like student.findById({author: token.id}).
You don't have to make jwt module but you can use jsonwebtoken that already provided.
var mongoose = require('mongoose');
var bcrypt = require('bcrypt-nodejs');
var UserSchema = new mongoose.Schema({
email: {
type: string,
unique: true,
required: true,
trim: true
},
password: {
type: string,
required: true
},
authtokens: {
type: [{ type: mongoose.Schema.Types.ObjectId, ref: 'AuthToken' }]
}
});
//hashing a password before saving it to the database
UserSchema.pre('save', function (next) {
if (this.isNew) {
bcrypt.gensalt(10, function(err, salt) {
if (err) return next(err);
bcrypt.hash(this.password, salt, null, function (err, hash){
if (err) return next(err);
this.password = hash;
console.log('user.password ', this.password);
next();
});
});
} else next();
});
I call this from a controller:
'use strict';
var mongoose = require('mongoose'),
User = mongoose.model('User'),
AuthToken = mongoose.model('AuthToken');
exports.createUser = function(req, res, next) {
if (req.body.email && req.body.password && req.body.passwordConf) {
var userData = {
email: req.body.email,
password: req.body.password,
passwordConf: req.body.passwordConf
};
//use schema.create to insert data into the db
User.create(userData, function (err, user) {
console.log('user created ', user.password);
if (err) {
return next(err);
} else {
return res.redirect('/profile');
}
});
} else {
var err = new Error("Missing parameters");
err.status = 400;
next(err);
}
};
When a createUser is called with email user#email.com, password password, I get the output:
user.password $2a$10$wO.6TPUm5b1j6lvHdCi/JOTeEXHWhYernWU.ZzA3hfYhyWoOeugcq
user created password
Also, looking directly in the database, I see this user with plain text password -> password.
Why is user having plaintext password in the database. How can I store the hash instead?
In short, you forgot you were going into a callback which has a different functional scope and you're still referring to this, which is at that time not actually the "model" instance.
To correct this, take a copy of this before you do anything like launching another function with a callback:
UserSchema.pre('save', function(next) {
var user = this; // keep a copy
if (this.isNew) {
bcrypt.genSalt(10, function(err,salt) {
if (err) next(err);
bcrypt.hash(user.password, salt, null, function(err, hash) {
if (err) next(err);
user.password = hash;
next();
});
});
}
});
An alternate approach of course is to modernize things and use Promise results with async/await. The bcrypt library which is actually the "core" and not a fork does this right out of the box:
UserSchema.pre('save', async function() {
if (this.isNew) {
let salt = await bcrypt.genSalt(10);
let hash = await bcrypt.hash(this.password, salt);
this.password = hash;
}
});
Aside from the modern approach being generally cleaner code, you also don't need to change the scope of this since we don't "dive in" to another function call. Everything gets changed in the same scope, and of course awaits the async calls before continuing.
Full Example - Callback
const { Schema } = mongoose = require('mongoose');
const bcrypt = require('bcrypt-nodejs');
const uri = 'mongodb://localhost/crypto';
var userSchema = new Schema({
email: String,
password: String
});
userSchema.pre('save', function(next) {
var user = this; // keep a copy
if (this.isNew) {
bcrypt.genSalt(10, function(err,salt) {
if (err) next(err);
bcrypt.hash(user.password, salt, null, function(err, hash) {
if (err) next(err);
user.password = hash;
next();
});
});
}
});
const log = data => console.log(JSON.stringify(data, undefined, 2));
const User = mongoose.model('User', userSchema);
(async function() {
try {
const conn = await mongoose.connect(uri);
await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));
await User.create({ email: 'ted#example.com', password: 'password' });
let result = await User.findOne();
log(result);
} catch(e) {
console.error(e)
} finally {
process.exit()
}
})()
Full Example - Promise async/await
const { Schema } = mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const uri = 'mongodb://localhost/crypto';
var userSchema = new Schema({
email: String,
password: String
});
userSchema.pre('save', async function() {
if (this.isNew) {
let salt = await bcrypt.genSalt(10);
let hash = await bcrypt.hash(this.password, salt);
this.password = hash;
}
});
const log = data => console.log(JSON.stringify(data, undefined, 2));
const User = mongoose.model('User', userSchema);
(async function() {
try {
const conn = await mongoose.connect(uri);
await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));
await User.create({ email: 'ted#example.com', password: 'password' });
let result = await User.findOne();
log(result);
} catch(e) {
console.error(e)
} finally {
process.exit()
}
})()
Both show the password correctly encrypted, since we actually set the value in the model instance:
{
"_id": "5aec65f4853eed12050db4d9",
"email": "ted#example.com",
"password": "$2b$10$qAovc0m0VtmtpLg7CRZmcOXPDNi.2WbPjSFkfxSUqh8Pu5lyN4p7G",
"__v": 0
}
I'm making a User Authentication with passport. First I created a default Admin User. Now this Admin must able to create users but not any other users. For this I created a Admin user in Database. Now my Question is how to create the other users by the Admin and as well only this Admin should have access to all API's routes but not for any other Users how to protect the API's? In server.js file i created middleware function as
//Catch unauthorized errors
app.use(function (err, req, res, next) {
if(err.name === 'UnauthorizedError') {
res.status(401);
res.json({"message": err.name + ":" + err.message});
}
});
Please help with this. I hope you guys don't mind for posting such a long files.
'authentication.js'
'use strict';
var passport = require('passport'),
mongoose = require('mongoose'),
Users = mongoose.model('Users');
var authentication = {
register: function(req, res, name, email, password) {
var userData = req.body;
var user = new Users({
email: userData.email,
name: userData.name,
});
user.setPassword(userData.password);
if(!user) {
res.status(400).send({error: 'All fields required'});
}
user.save(function(err, result) {
if(err) {
console.log('Could not save the User');
res.status(500).send({error: 'Could not save the User'});
}else {
res.send('New User Created Successfully');
}
});
},
login: function (req, res) {
if(!req.body.email || !req.body.password) {
res.status(400).send({"message": "All fields required"});
return;
}
passport.authenticate('local', function (err, user, info) {
var token;
if (err) {
res.status(404).send({err: 'An Error Occured'});
return;
}
if(user) {
token = user.generateJwt();
res.status(300).send({"token": token});
}else {
res.status(401).send('Unauthorized User');
}
});
}
};
module.exports = authentication;
'user-model.js'
'use strict';
var mongoose = require('mongoose'),
crypto = require('crypto'),
jwt = require('jsonwebtoken'),
Schema = mongoose.Schema;
var userSchema = new mongoose.Schema({
email: {
type: String,
required: true,
unique: true
},
name: {
type: String,
required: true
},
hash: String,
salt: String
});
userSchema.methods.setPassword = function (password) {
this.salt = crypto.randomBytes(16).toString('hex');
this.hash = crypto.pbkdf2Sync(password, this.salt, 1000, 64).toString('hex');
};
//Validating a submitted password
userSchema.methods.validPassword = function (password) {
var hash = crypto.pbkdf2Sync(password, this.salt, 1000, 64).toString('hex');
return this.hash === hash;
};
//Generating a JSON Web Token
userSchema.methods.generateJwt = function () {
var expiry = new Date();
expiry.setDate(expiry.getDate() + 7);
return jwt.sign({
_id: this._id,
email: this.email,
name: this.name,
exp: parseInt(expiry.getTime() / 1000)
}, process.env.JWT_SECRET);
};
var User = mongoose.model('Users', userSchema);
var user = new User();
user.name = 'Arjun Kumar';
user.email = 'arjun#kumar.com';
user.setPassword('myPassword');
user.save();
'user-route.js'
'use strict';
var express = require('express'),
userRoute = express.Router(),
jwt = require('express-jwt'),
authentication = require('../controllers/authentication');
var auth = jwt({
secret: process.env.JWT_SECRET,
userProperty: 'payload'
});
userRoute.post('/:adminuserid/register', auth, authentication.register)
.post('/login', authentication.login);
module.exports = userRoute;
'passport.js'
var passport = require('passport'),
LocalStrategy = require('passport-local').Strategy,
mongoose = require('mongoose'),
Users = mongoose.model('Users');
passport.use(new LocalStrategy({usernameField: 'email'}, function (username, password, done) {
Users.findOne({ email: username }, function (err, user) {
if (err) {
return done(err);
}
if (!user) {
return done(null, false, {
message: 'Incorrect username.'
})
}
if (!user.validPassword(password)) {
return done(null, false, {
message: 'Incorrect password.'
});
}
return done(null, user);
});
}));
One thing you can do it to put all your functions in a conditional like this to give access only to admin:
If(req.user.email === your admin email) {
Your function
}
This should go under the routes that you want only the admin have access to.
Or if you have several admins , then you should alter your schema a bit and add an admin : Number which you can later declare for example any user with admin:1 are system administrators otherwise not .
I hope I understood your question correctly.
Good luck