Why can't I access a mongoose schema's method? - node.js

I have this Mongoose schema in a Nodejs application:
const mongoose = require('mongoose'),
Schema = mongoose.Schema,
sodium = require('sodium').api;
const UserSchema = new Schema({
username: {
type: String,
required: true,
index: { unique: true }
},
salt: {
type: String,
required: false
},
password: {
type: String,
required: true
}
});
UserSchema.methods.comparePassword = function(candidatePassword, targetUser) {
let saltedCandidate = candidatePassword + targetUser.salt;
if (sodium.crypto_pwhash_str_verify(saltedCandidate, targetUser.password)) {
return true;
};
return false;
};
module.exports = mongoose.model('User', UserSchema);
And I created this routes file.
const _ = require('lodash');
const User = require('../models/user.js'); // yes, this is the correct location
module.exports = function(app) {
app.post('/user/isvalid', function(req, res) {
User.find({ username: req.body.username }, function(err, user) {
if (err) {
res.json({ info: 'that user name or password is invalid. Maybe both.' });
};
if (user) {
if (User.comparePassword(req.body.password, user)) {
// user login
res.json({ info: 'login successful' });
};
// login fail
res.json({ info: 'that user name or password is invalid Maybe both.' });
} else {
res.json({ info: 'that user name or password is invalid. Maybe both.' });
};
});
});
};
I then use Postman to make a call to 127.0.0.1:3001/user/isvalid with an appropriate Body content. The terminal says tell me TypeError: User.comparePassword is not a function and crashes the app.
Since the if (user) bit passes, that indicates to me that I have properly retrieved a document from Mongo and have an instance of the User schema. Why is the method not valid?
eta: the module export I failed to copy/paste originally

This creates instance method:
UserSchema.methods.comparePassword = function(candidatePassword, targetUser) {
// ...
};
If you want a static method use this:
UserSchema.statics.comparePassword = function(candidatePassword, targetUser) {
// ...
};
Static methods are when you want to call it as User.comparePassword().
Instance methods are when you want to call it as someUser.comparePassword() (which in this case would make a lot of sense so that you wouldn't have to pass the user instance explicitly).
See the documentation:
http://mongoosejs.com/docs/guide.html#methods
http://mongoosejs.com/docs/guide.html#statics

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.

mongoose Model.create function returns undefined

The above query returns a 200 when I try to create a User, but whenever I log into MongoDB there is no collections created. Can anyone help ?
//user model
const userSchema = mongoose.Schema({
name: {
type : String,
required : true,
trim : true
},
email: {
type: String,
required: true,
unique: true,
lowercase: true,
validate: value => {
if(!validator.isEmail(value)){
throw new Error({error : 'Invalid email address'})
}
}
},
password: {
type: String,
required: true,
minLength: 5
},
// a user can have multiple jobs
jobs : [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Job'
}],
tokens: [{
token: {
type: String,
required: true
}
}]
})
const User = mongoose.model('User', userSchema)
module.exports = User
// user functions written
createUser(name, email, password){
return User.create({name: name, email: email, password : password}, (err, docs) => {
if(err){
throw err.message;
}
});
}
//routes.js
// user create
router.post('/users', async(req, res) => {
try{
const {name, email, password } = req.body
const user = userManager.createUser(name, email, password); [1]
res.status(200).json(user)
}
catch(error) {
res.status(400).send({error : error.message})
}
})
The line[1] returns undefined. Why ?
note : all module requirements are fulfilled
After you create the schema you need to create a Model FROM that schema.
Example from MDN:
// Define schema
var Schema = mongoose.Schema;
var SomeModelSchema = new Schema({
a_string: String,
a_date: Date
});
// Compile model from schema
var SomeModel = mongoose.model('SomeModel', SomeModelSchema );
Now after you create the model you can use SomeModel.create
EDIT:
line[1] will always return undefined because you are using callbacks and only way to get value out of callback is either push another callback(I would really discourage that). But best way is to use Promises now mongoose by default supports `Promises. So, basically for promises it will be,
// user functions written
async function createUser(name, email, password){
try {
return await User.create({ name: name, email: email, password: password });
} catch (err) {
throw err.message;
}
}
In the router adda await:
const user = await userManager.createUser(name, email, password);
The problem is you call an asynchronous function synchronously. It returned undefined because the function hasn't been resolved yet.
A solution could be to use promises or async/await.
Example:
async createUser(name, email, password) {
const createdUser = await User.create({name,email,password});
return creaatedUser;
}
Something I ran into was you need to pass in an empty object if your not setting any fields - i.e.
Good: Model.create({})
Bad: Model.create()

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

Unhandled rejection TypeError: dbUser.validPassword is not a function Sequelize Node React Passport

I am attempting to use Passport Authentication on my web app. I am using Sequelize ORM, Reactjs front-end and express and node back end. Right now, when I register a user everything works fine. the problem comes when I try to login. I see the user querying the DB to find the user with correct email, but when it is time to compare passwords, i am catching an error.
"Unhandled rejection TypeError: dbUser.validPassword is not a function"
here is my config/passport.js file:
var passport = require("passport");
var LocalStrategy = require("passport-local").Strategy;
var db = require("../models");
// Telling passport we want to use a Local Strategy. In other words, we
want login with a username/email and password
passport.use(new LocalStrategy(
// Our user will sign in using an email, rather than a "username"
{
usernameField: "email"
},
function(email, password, done) {
// When a user tries to sign in this code runs
db.User.findOne({
where: {
email: email
}
}).then(function(dbUser) {
// If there's no user with the given email
if (!dbUser) {
return done(null, false, {
message: "Incorrect email."
});
}
// If there is a user with the given email, but the password the user
gives us is incorrect
else if (!dbUser.validPassword(password)) {
return done(null, false, {
message: "Incorrect password."
});
}
// If none of the above, return the user
return done(null, dbUser);
});
}
));
// In order to help keep authentication state across HTTP requests,
// Sequelize needs to serialize and deserialize the user
// Just consider this part boilerplate needed to make it all work
passport.serializeUser(function(user, cb) {
cb(null, user);
});
passport.deserializeUser(function(obj, cb) {
cb(null, obj);
});
// Exporting our configured passport
module.exports = passport;
Here is my User Model:
var bcrypt = require("bcrypt-nodejs");
[![enter image description here][1]][1]module.exports = function(sequelize, DataTypes){
var User = sequelize.define("User", {
email: {
type: DataTypes.STRING,
allowNull: false,
validate: {
isEmail: true
}
},
password: {
type: DataTypes.STRING,
allowNull: false
},
},{
classMethods: {
associate: function(models) {
User.hasOne(models.Educator, {
onDelete: "cascade"
});
User.hasOne(models.Expert, {
onDelete: "cascade"
});
}
},
instanceMethods: {
validPassword: function(password) {
return bcrypt.compareSync(password, this.password);
}
},
// Hooks are automatic methods that run during various phases of the User Model lifecycle
// In this case, before a User is created, we will automatically hash their password
hooks: {
beforeCreate: function(user, options) {
console.log(user, options )
user.password = bcrypt.hashSync(user.password, bcrypt.genSaltSync(10), null);
}
}
})
return User;
}
I am also including an image of the error.
As of sequelize version >4, it has changed the way instance methods are defined.
They follow a more class based approach now,
A sample from the Docs for how it has to be done
const Model = sequelize.define('Model', { ... });
// Class Method
Model.associate = function (models) { ...associate the models };
// Instance Method
Model.prototype.someMethod = function () {..}
The syntax you are using corresponds to sequelize < 4.

node js passport js change user's password

I'm creating an application using node js. in this application i already completed user login and registration via passport js. So now i need to provide access to the logged user to change there password. So i'm trying to do this in my own way but when i run this process the changed password doesn't updated and save it to the logged user's mongoose document. I'll provide the code that i used to that process. So i'm requesting you guys please let me know how can i do this in with my program.
This is my POST route for the change password.
app.post('/changePass/:hash', isLoggedIn, function(req, res){
cph.findOne({hash: req.params.hash}).populate('userId', "local.password -_id").exec(function(err, hash){
if(err) throw err;
if(validator.isEmpty(req.body.currentPassword) || validator.isEmpty(req.body.newPassword) || validator.isEmpty(req.body.confirmPassword)){
res.render('admin/settings/pages/cup/cpf', {
user: req.user,
message: 'Fields must be required',
data: hash
});
}
else {
if(!bcrypt.compareSync(req.body.currentPassword, hash.userId.local.password)){
res.render('admin/settings/pages/cup/cpf', {
user: req.user,
message: 'Current password is incurrect',
data: hash
});
}
else {
if(req.body.newPassword != req.body.confirmPassword){
res.render('admin/settings/pages/cup/cpf', {
user: req.user,
message: 'New password and confirm password do not match',
data: hash
});
}
else {
cph.update({$set:{'userId.local.password': bcrypt.hashSync(req.body.confirmPassword, bcrypt.genSaltSync(8), null)}}, function(){
console.log('Success')
});
}
}
}
});
});
This is the mongoose collection that creating a hash to change the password sending as a combined link to the logged user's email.
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var bcrypt = require('bcrypt-nodejs');
var cpHashSchema = Schema({
userId: {
type: Schema.ObjectId,
ref: 'users'
},
hash: {
type: String
}
});
module.exports = mongoose.model('changepasswordHash', cpHashSchema);
This is the user's collection
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var bcrypt = require('bcrypt-nodejs');
var userSchema = Schema({
active: {
type: Boolean,
default: false
},
first: {
type: String
},
last: {
type: String
},
email: {
type: String
},
local: {
username: {
type: String
},
password: {
type: String
}
},
joined: {
type: Date,
default: Date.now
},
usertype: {
type: String,
default: 'user'
}
});
userSchema.methods.generateHash = function(password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
};
userSchema.methods.validPassword = function(password) {
return bcrypt.compareSync(password, this.local.password);
};
module.exports = mongoose.model('users', userSchema);
These are the source code that i'm using to build this application. So guys please help me to complete this application.
thank you
First of all - you trying to update changepasswordHash collection with fields from another table. MongoDB couldn't update related records.
You have to update users collection using userId something like:
users.update({_id: hash.userId._id}, {$set: {'local.password': newPass}}, callbackHere)

Resources