Mongo: hash password only being created on insert not update - node.js

Below is a JS for Account mongoose model file for my mongo account collection with a couple of methods. When a an account is created the hashedPassword field is created however how its not working when I update the document. Is it the case that the virtuals are only called on insert rather than update ?
'use strict';
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
crypto = require('crypto');
var AccountSchema = new Schema({
email: {type: String,unique: true,required: true},
username: {type: String,unique: true,required: true},
group:{type:Schema.Types.ObjectId,ref:"Groups",require:false},
hashedPassword: String,
salt: String,
name: {type: String,require:false},
active: {type: Boolean,require:false},
});
/**
* Virtuals
*/
AccountSchema.virtual('password').set(function(password) {
this._password = password;
this.salt = this.makeSalt();
this.hashedPassword = this.encryptPassword(password);
})
.get(function() {
return this._password;
});
AccountSchema.virtual('user_info').get(function () {
return { '_id': this._id, 'username': this.username, 'email': this.email };
});
/**
* Validations
*/
AccountSchema.path('email').validate(function (email) {
var emailRegex = /^([\w-\.]+#([\w-]+\.)+[\w-]{2,4})?$/;
return emailRegex.test(email);
}, 'The specified email is invalid.');
AccountSchema.path('email').validate(function(value, respond) {
respond(true);
}, 'The specified email address is already in use.');
AccountSchema.methods = {
authenticate: function(plainText) {
return this.encryptPassword(plainText) === this.hashedPassword;
},
makeSalt: function() {
return crypto.randomBytes(16).toString('base64');
},
encryptPassword: function(password) {
console.log('encryptPassword')
if (!password || !this.salt) return '';
var salt = new Buffer(this.salt, 'base64');
return crypto.pbkdf2Sync(password, salt, 10000, 64).toString('base64');
}
};
mongoose.model('Account', AccountSchema);
In the controller
$scope.register = function(form) {
Auth.createUser({
provider : "local",
_id: $scope.userData._id,
email: $scope.userData.email,
username: $scope.userData.name,
level : $scope.userData.level,
group : $scope.userData.group,
name : $scope.userData.name,
password: $scope.userData.password,
active: $scope.userData.active
},
function(err) {
$scope.errors = {};
if (!err) {
$state.go('users');
} else {
angular.forEach(err.errors, function(error, field) {
form[field].$setValidity('mongoose', false);
$scope.errors[field] = error.type;
});
}
}
);
The services.js where the createUser func is called
/* Services */
app.factory('User', function ($resource) {
return $resource('/auth/users/:id/', {},
{
'update': {
method:'PUT'
}
});
});
app.factory('Session', function ($resource) {
return $resource('/auth/session/');
});
app.factory('Auth', function Auth($location, $rootScope, Session, User, $cookieStore) {
$rootScope.currentUser = $cookieStore.get('user') || null;
$cookieStore.remove('user');
return {
createUser: function(userinfo, callback) {
var cb = callback || angular.noop;
User.save(userinfo,
function(user) {
$rootScope.currentUser = user;
return cb();
},
function(err) {
return cb(err.data);
});
},
currentUser: function() {
Session.get(function(user) {
$rootScope.currentUser = user;
});
}
};
});

Related

Mongoose Schema.method() is not working, and showing an error message

I am taking password input from the user and encrypting the password using crypto, then saving into the database. This is my code, here I am storing the encrypted password into the encry_password property that comes from the userSchema. But, this is giving me error that "this.securePassword" is not a function.
const mongoose = require("mongoose");
const crypto = require("crypto");
const { v1: uuidv1 } = require("uuid");
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true,
maxlength: 32,
trim: true,
},
lastname: {
type: String,
maxlength: 32,
trim: true,
},
email: {
type: String,
trim: true,
required: true,
unique: true,
},
usrinfo: {
type: String,
trim: true,
},
encry_password: {
type: String,
required: true
},
salt: String,
role: {
type: Number,
default: 0,
},
purchases: {
type: Array,
default: [],
},
}, { timestamps: true });
userSchema.virtual("password")
.set((password) => {
this._password = password;
this.salt = uuidv1();
this.encry_password = securePassword(password, uuidv1());
console.log(this.encry_password);
})
.get(() => {
return this._password;
});
// const authenticate = function (plainPassword, encry_password) {
// return securePassword(plainPassword) === encry_password;
// };
const securePassword = function (plainPassword, salt) {
if (!plainPassword) return "";
try {
return crypto.createHmac("sha256", salt).update(plainPassword).digest("hex");
} catch (error) {
return "";
}
};
module.exports = mongoose.model("User", userSchema);
Route for user signup
exports.signup = (req, res) => {
console.log(req.body);
const user = new User(req.body);
user.save((err, user) => {
if (err) {
console.log(err);
res.status(400).json({
err: "Note able to save the user in database"
});
} else {
res.json(user);
}
});
};
first of all, in this situation you shouldn't use virtual
Virtuals
Virtuals are document properties that you can get and set but that do not get persisted to MongoDB. The getters are useful for formatting or combining fields, while setters are useful for de-composing a single value into multiple values for storage.
but in the scope of virtual, this cannot access to method, you can not access to the method like your manner, it's a example of method usage in mongoose
const Animal = mongoose.model('Animal', animalSchema);
const dog = new Animal({ type: 'dog' });
dog.findSimilarTypes((err, dogs) => {
console.log(dogs); // woof
});
you can check the method documantation:
if you want just access to securePassword in your manner you can like this and delete method mongoose complately because this is not the place to use method:
UserSchema.virtual("password")
.set((password) => {
this._password = password;
this.salt = uuidv1();
console.log("This is running");
this.encry_password = securePassword(password, this.salt);
console.log(encry_password);
})
.get(() => {
return this._password;
});
const authenticate = function (plainPassword, encry_password) {
return securePassword(plainPassword) === encry_password;
};
const securePassword = function (plainPassword, salt) {
if (!plainPassword) return "";
try {
return crypto
.createHmac("sha256", salt)
.update(plainPassword)
.digest("hex");
} catch (error) {
return "";
}
};
if you want to create authenticate service, change your manner, and don't use virtual for password and use pre save
before saving information about users in db this tasks will be done
check the pre documentation
userSchema.pre("save", async function (next) {
try {
this.password = securePassword (plainPassword, salt);
} catch (error) {
console.log(error);
}
});
after created a hash password save informations like this :
const userSchema = new mongoose.Schema({
.
.
.
password: { //convert encry_password to password
type: String,
}
.
.
.
}, { timestamps: true });
//every time want to user save this method called
userSchema.pre('save', function (next) {
this.salt = uuidv1()
this.password = securePassword(this.password, this.salt)
next()
})
//for have a clean routes, you can create a static methods
userSchema.statics.Create = async (data) => {
let model = new User(data);
let resUser = await model.save(); //save your user
return resUser;
};
const securePassword = function (plainPassword, salt) {
if (!plainPassword) return "";
try {
return crypto.createHmac("sha256", salt).update(plainPassword).digest("hex");
} catch (error) {
return "";
}
};
let User = mongoose.model("User", userSchema)
module.exports = {User};
change controller like this :
let {User} = require("./path of user schema")
exports.signup = async (req, res) => {
try {
console.log(req.body);
const user = await User.create(req.body); //create a user
res.json(user);
} catch (error) {
console.log(err);
res.status(400).json({
err: "Note able to save the user in database",
});
}
};
NOTE : in req.body, name of password field, should be password
It looks like the scope of the securePassword function is defined inside userSchema, and you're trying to call it in userSchema.virtual.

Result not getting stored in Google datastore DB

Not able to save the data in Google Datastore DB not getting any error, can somebody help me to find the fix
Console.log result as below
entityKey: Key { namespace: undefined, kind: 'User', path: [Getter] },
entityData:
{ firstname: 'Abcd',
lastname: 'Abcd',
email: 'abcd#gmail.com',
password: '123454',
createdOn: 'Abcd',
[Symbol(KEY)]: Key { namespace: undefined, kind: 'User', path: [Getter] } },
Ref - https://www.npmjs.com/package/gstore-node
const express = require('express');
const router = express.Router();
const { check, validationResult } = require('express-validator');
var User =require('../models/user');
//get register page
router.get('/register',function(req,res){
res.render('register')
});
//get login page
router.get('/login',function(req,res){
res.render('login')
});
router.post('/register', [
check('Name').isEmpty().withMessage('The Name is required'),
check('Email').isEmail().withMessage('Email is requried'),
//check('Password').isEmpty().withMessage('pass is requried'),
//check('Password','Password is Requried').isEmpty(),
// check('Password2','Password Not Match').equals('password2'),
], (req, res,next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.render('register',{
error:errors.mapped()
})
}else{
console.log()
const newUser = new User ({
firstname:req.body.name,
lastname:req.body.name,
email :req.body.Email,
password :req.body.Password,
createdOn:req.body.name
});
console.log("Data1",newUser)
const createUser = (req, res) => {
const entityData = User.sanitize(req.body);
const user = new User(entityData);
console.log("Data2",createUser)
user.save()
.then((entity) => {
res.json(entity.plain());
})
.catch((err) => {
// If there are any validation error on the schema
// they will be in this error object
res.status(400).json(err);
})
};
req.flash('success_msg','you are registered and can login now');
res.redirect('/users/login');
}
});
module.exports=router;
const { Gstore, instances } = require('gstore-node');
const { Datastore } = require('#google-cloud/datastore');
const gstore = new Gstore();
const datastore = new Datastore({
projectId: 'sinuous250616',
});
gstore.connect(datastore);
// Save the gstore instance
instances.set('unique-id', gstore);
const bcrypt = require('bcrypt');
// Retrieve the gstore instance
const ggstore = instances.get('unique-id');
const { Schema } = ggstore;
/**
* A custom validation function for an embedded entity
*/
const validateAccessList = (value, validator) => {
if (!Array.isArray(value)) {
return false;
}
return value.some((item) => {
const isValidIp = !validator.isEmpty(item.ip) && validator.isIP(item.ip, 4);
const isValidHostname = !validator.isEmpty(item.hostname);
return isValidHostname && isValidIp;
});
}
//Create the schema for the User Model
const userSchema = new Schema({
firstname: { type: String, required: true },
lastname: { type: String, optional: true },
email: { type: String, validate: 'isEmail', required: true },
password: { type: String, read: false, required: true },
createdOn: { type: String, default: gstore.defaultValues.NOW, write: false, read: false }
});
/**
* List entities query shortcut
*/
const listSettings = {
limit: 15,
order: { property: 'lastname' }
};
userSchema.queries('list', listSettings);
/**
* Pre "save" middleware
* Each time the entity is saved or updated, if there is a password passed, it will be hashed
*/
function hashPassword() {
// scope *this* is the entity instance
const _this = this;
const password = this.password;
if (!password) {
return Promise.resolve();
}
return new Promise((resolve, reject) => {
bcrypt.genSalt(5, function onSalt(err, salt) {
if (err) {
return reject(err);
};
bcrypt.hash(password, salt, null, function onHash(err, hash) {
if (err) {
// reject will *not* save the entity
return reject(err);
};
_this.password = hash;
// resolve to go to next middleware or save method
return resolve();
});
});
});
// add the "pre" middleware to the save method
userSchema.pre('save', hashPassword);
/**
* Export the User Model
* It will generate "User" entity kind in the Datastore
*/
module.exports = gstore.model('User', userSchema);
*I think there is a problem with User model **
You should have a User model like this in /models/user.js (put models at the root of your application) to define User:
const { instances } = require('gstore-node');
const bscrypt = require('bcrypt-nodejs');
// Retrieve the gstore instance
const gstore = instances.get('unique-id');
const { Schema } = gstore;
var usersSchema = new Schema({
firstname:{type:String},
lastname:{type:String},
email:{type:String},
password :{type:String},
createdOn: Date
})
var User = gstore.model('User', usersSchema);
module.exports = User;
And you forgot to use to save with save()
var newUser = new User ({
firstname:req.body.name,
lastname:req.body.name,
email :req.body.Email,
password :req.body.Password,
createdOn: new Date() // there is a problem here.... use new Date()
});
newUser.save(); //<======= it is abscent so it won't save

Generate hashed password in findOneAndUpdate

Here is my query for findOneAndUpdate
const { email, password, id } = req.body
Artist.findOneAndUpdate({ _id: id }, { $set: req.body }).then((artist) => {
return res.json({
success: true,
message: "Invitation sent."
});
})
And here is my schema
var artistSchema = new mongoose.Schema({
name: { type: String, default: '' },
password: { type: String, default: '' }
})
artistSchema.pre('findOneAndUpdate', function (next) {
console.log('------------->>>>>> findOneAndUpdate: ');
console.log(this.password) // why undefined?
next();
});
I want to create a hashed password when user update details
const { email, password, id } = req.body;
Artist.findByIdAndUpdate(id, { $set: req.body }).then(artist => {
return res.json({
success: true,
message: "Invitation sent."
});
});
Example with bcrypt
var artistSchema = new mongoose.Schema({
name: { type: String, default: "" },
password: { type: String, default: "" }
});
artistSchema.pre("update", function(next) {
bcrypt.hash(this.password, 10, function(err, hash) {
if (err) return next(err);
this.password = hash;
next();
});
});
let crypto = require('crypto');
let mongoose = require('../mongoose'),
Schema = mongoose.Schema;
Then Schema
let schema = new Schema({
name: {
type: String,
default: ''
},
hashedPassword: {
type: String,
required: true
},
salt: {
type: String,
required: true
}
});
Then methods and virtuals
schema.methods.encryptPassword = function(password){
return crypto.createHmac('sha1', this.salt).update(password).digest('hex');
};
schema.virtual('password').set(function(password){
this._plainPassword = password;
this.salt = Math.random() + '';
this.hashedPassword = this.encryptPassword(password);
}).get(function(){ return this._plainPassword; });
You can check password like that
schema.methods.checkPassword = function(password){
return this.encryptPassword(password) === this.hashedPassword;
};
Export module
module.exports.Artist = mongoose.model('Artist', schema);
Then just save like before
const { email, password, id } = req.body;
Artist.findOneAndUpdate({ _id: id }, { $set: req.body }).then((artist) => {
return res.json({
success: true,
message: "Invitation sent."
});
});
But I sugest you also to use statics. For example:
schema.statics.updateUser = function (data){
// your code
}
And then you can use
Artist.updateUser(req.body).then((res) => {
// response
})
The answer: Writeconsole.log(JSON.stringify(this._update));
My solution for check blank password.
userSchema.pre('findOneAndUpdate', function() {
console.log(JSON.stringify(this._update));
if (this._update.password.length == 0) {
this._update = {
"fullname": this._update.fullname
};
}
else {
this._update = {
"fullname": this._update.fullname,
"password": bcrypt.hashSync(this._update.password, bcrypt.genSaltSync(8), null)
};
}
});

Mongoose Populate users is returning a 404 (forbidden)

I am trying to populate the users array in a project but I am getting GET http://localhost:9000/api/users 403 (Forbidden) (if logged in) and GET http://localhost:9000/api/users 403 (Unauthorised) (if not logged in). So I am fairly confident that my problem is because Users have Authentication attached to them and when I am calling the list projects function.
All I am wanting to do is simply get an array of users for each project then using the objectid populate that list with the users name & profile_image.
Here is what I have:
exports.index = function (req, res) {
Project
.find(function (err, projects) {
if (err) { //handle error
return handleError(res, err);
}
return projects;
})
.populate('users')
.exec(function (err, projects) {
if (err) { //handle error
return handleError(res, err);
}
return res.json(200, projects);
});
};
ProjectSchemas:
'use strict';
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var ProjectSchema = new Schema({
name: String,
type: String,
users: [{ type: Schema.Types.ObjectId, ref: 'User' }]
});
ProjectSchema.statics = {
load: function (id, cb) {
this.findOne({ _id : id })
.populate('users', 'name profile_image')
.exec(cb);
}
}
module.exports = mongoose.model('Project', ProjectSchema);
UserSchema:
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var crypto = require('crypto');
var authTypes = ['github', 'twitter', 'facebook', 'google'];
var UserSchema = new Schema({
name: String,
email: { type: String, lowercase: true },
profile_image: String,
role: {
type: String,
default: 'user'
},
hashedPassword: String,
provider: String,
salt: String,
facebook: {},
twitter: {},
google: {},
github: {}
});
/**
* Virtuals
*/
UserSchema
.virtual('password')
.set(function(password) {
this._password = password;
this.salt = this.makeSalt();
this.hashedPassword = this.encryptPassword(password);
})
.get(function() {
return this._password;
});
// Public profile information
UserSchema
.virtual('profile')
.get(function() {
return {
'name': this.name,
'role': this.role
};
});
// Non-sensitive info we'll be putting in the token
UserSchema
.virtual('token')
.get(function() {
return {
'_id': this._id,
'role': this.role
};
});
/**
* Validations
*/
// Validate empty email
UserSchema
.path('email')
.validate(function(email) {
if (authTypes.indexOf(this.provider) !== -1) return true;
return email.length;
}, 'Email cannot be blank');
// Validate empty password
UserSchema
.path('hashedPassword')
.validate(function(hashedPassword) {
if (authTypes.indexOf(this.provider) !== -1) return true;
return hashedPassword.length;
}, 'Password cannot be blank');
// Validate email is not taken
UserSchema
.path('email')
.validate(function(value, respond) {
var self = this;
this.constructor.findOne({email: value}, function(err, user) {
if(err) throw err;
if(user) {
if(self.id === user.id) return respond(true);
return respond(false);
}
respond(true);
});
}, 'The specified email address is already in use.');
var validatePresenceOf = function(value) {
return value && value.length;
};
/**
* Pre-save hook
*/
UserSchema
.pre('save', function(next) {
if (!this.isNew) return next();
if (!validatePresenceOf(this.hashedPassword) && authTypes.indexOf(this.provider) === -1)
next(new Error('Invalid password'));
else
next();
});
/**
* Methods
*/
UserSchema.methods = {
/**
* Authenticate - check if the passwords are the same
*
* #param {String} plainText
* #return {Boolean}
* #api public
*/
authenticate: function(plainText) {
return this.encryptPassword(plainText) === this.hashedPassword;
},
/**
* Make salt
*
* #return {String}
* #api public
*/
makeSalt: function() {
return crypto.randomBytes(16).toString('base64');
},
/**
* Encrypt password
*
* #param {String} password
* #return {String}
* #api public
*/
encryptPassword: function(password) {
if (!password || !this.salt) return '';
var salt = new Buffer(this.salt, 'base64');
return crypto.pbkdf2Sync(password, salt, 10000, 64).toString('base64');
}
};
module.exports = mongoose.model('User', UserSchema);
I found a solution, I needed to make a new route for public users that didn't require authentication:
router.get('/public', controller.public);
I then added it to controller:
/**
* Get public list of users
*/
exports.public = function(req, res) {
User.find({}, 'name profileImage',function (err, users) {
if(err) return res.send(500, err);
res.json(200, users);
});
};
Then in my state provider I resolved users to use /users/public:
resolve: {
users: function(usersModel) {
return usersModel.one('public').getList()
}
}
And this has now solved the 403 error!

sequelize + dataInsert

In my node app i am using sequelize as an ORM for my postgresql database.The following is my model:
User:
var bcrypt = require('bcrypt'),
crypto = require('crypto');
var authTypes = ['github', 'twitter', 'facebook', 'google'];
var map_attributes = function() {
var obj = new Object(),
ctx = this;
ctx.attributes.forEach(
function(attr) {
obj[attr] = ctx[attr];
});
return obj;
};
var validatePresenceOf = function(value) {
return value && value.length;
};
module.exports = function(sequelize, DataTypes) {
return sequelize.define('user', {
name: {
type: DataTypes.STRING,
validate: {
len: {
args: 1,
msg: "Name cannot be blank"
}
}
},
email: {
type: DataTypes.STRING,
validate: {
len: {
args: 1,
msg: "email cannot be blank"
}
}
},
username: {
type: DataTypes.STRING,
validate: {
len: {
args: 1,
msg: "username cannot be blank"
}
}
},
provider: DataTypes.STRING,
//hashed_password: String,
hashed_password: {
type: DataTypes.STRING,
set: function(v) {
var salt = bcrypt.genSaltSync(10);
var hash = bcrypt.hashSync(v, salt);
this.setDataValue('hashed_password', hash);
}
},
salt: DataTypes.STRING,
/*facebook: {},
twitter: {},
github: {},
google: {}*/
}, {
hooks: {
beforeValidate: function(next) {
if (!this.isNew) return next();
if (!validatePresenceOf(this.password) && authTypes.indexOf(this.provider) === -1) next(new Error('Invalid password'));
else next();
}
}
}, {
instanceMethods: {
authenticate: function(plainText) {
return this.encryptPassword(plainText) === this.hashed_password;
},
makeSalt: function() {
return Math.round((new Date().valueOf() * Math.random())) + '';
},
encryptPassword: function(password) {
if (!password) return '';
return crypto.createHmac('sha1', this.salt).update(password).digest('hex');
}
}
});
}
In this model i have to insert data into my database.How can i achieve this. I found this tutorial http://sequelizejs.com/docs/1.7.8/instances but its different from my design , here i am exporting the total model.Thanks in advance.
I tried for this:
index.js:
var Sequelize = require('sequelize');
var util = require('util');
var config = require('config').dbpostgres; // we use node-config to handle environments
// initialize database connection
var sequelize = new Sequelize(
config.dbname,
config.username,
config.password, {
dialect: 'postgres'
});
//var sequelize = new Sequelize('postgres://postgres:postgres#localhost:5432/Geocode', { dialect: 'postgres', protocol: 'postgres' });
// load models
var models = ['user'] //,'sciSphereModel', 'access_token', 'oauth_client', 'request_token', 'article'];
models.forEach(function(model) {
//console.log(model)
var sequelizeModel = sequelize.import(__dirname + '/' + model);
//console.log("seq=" + util.inspect(sequelizeModel));
//sequelizeModel.sync();
/*sequelizeModel.sync().success(function() {
console.log('DB error');
}).error(function(error) {
console.log(error);
})*/
sequelizeModel.sync();
//console.log("sequelizeModel=" + util.inspect(sequelizeModel))
var user = sequelizeModel.build({
name: 'kumar',
email: 'kumar#gmail.com',
username: 'kumar007',
provider: 'local',
hashed_password: 'tyegnaak',
salt: 'uopioann'
})
user.save().complete(function(err) {
console.log("Inside user save");
if ( !! err) {
console.log('The instance has not been saved:', err)
}
else {
console.log('We have a persisted instance now')
}
})
module.exports[model] = sequelizeModel; //Implement sync
});
// export connection
//sequelize.sync();
module.exports.sequelize = sequelize;
app.set('sequelize', sequelize);
Whether i am doing right?.Is this the way to achieve this?
Wouldn't it be sequelize.sync(); not sequelizeModel.sync(); because you'd want to syncronize any model changes with the db. You can also add force to your sync in case the table doesn't match your model.
sequelize.sync({ force: true }); // use to drop before create
I have never tried sync on a model, just create(), save(), find(), updateAttributes() etc.

Resources