why func2 works without ValidateBeforeSave:false but func1 doesn't? - node.js

const userSchema = new mongoose.Schema({
name: {
required: [true, 'A user must have a name'],
},,
password: {
required: [true, 'A password must be set'],
},
passwordConfirm: {
required: [true, 'password confimr must be set'],
validate: {
validator: function (val) {
return val === this.password;
},
},
},
passwordResetToken: String,
});
const User = mongoose.model('User', userSchema);
My schema with custom and built-in validators.
userSchema.methods.createResetToken = async function() {
const plainToken = "some string"
this.passwordResetToken = "some strange looking string"
return plainToken;
};
The function above sets values of some properties in the Schema and returns a string to func1.
const func1 = async (req, res, next) => {
const user = await User.findOne({ //getUser });
const plainToken = await user.createResetToken();
await user.save({ validateBeforeSave: false });
//more code
};
The function above runs only when ValidateBeforeSave is set to false.
const func2 = async function (req, res, next) {
let user = await User.findOne({
// get user
});
user.password = req.body.password;
user.passwordConfirm = req.body.passwordConfirm;
user.passwordResetExpiresAt = undefined;
user.passwordResetToken = undefined;
user = await user.save();
//more code
};
The function above runs without need of ValidateBeforeSave set to false.

Related

jest.spyOn "Number of calls: 0", but actual implementation still called

I want to spy on the hashPassword function in authController to see if it's called with the correct arguments from the user model, while keeping it's implementation.
number of calls 0
The mock function is being set but is not being called, meanwhile the original hashPassword function is being called. It seems that it can be tricky to make jest.spyOn work, but I think it should work as I am using 2 separate files.
logged controller and user
test
const request = require('supertest')
const app = require('../src/app.js')
const db = require('./db.js')()
const authController = require('../src/controllers/authController')
//in memory database
beforeAll(async () => await db.connect())
afterEach(async () => await db.clear())
afterAll(async () => await db.disconnect())
it('hashes the password', async () => {
const hashPassword = jest.spyOn(authController, 'hashPassword')
console.log(require('../src/controllers/authController'))
const user = await request(app)
.post('/auth/signup')
.send({ name: 'User', password: 'password', confirmPassword: 'password' })
console.log(user.body)
expect(hashPassword).toHaveBeenCalledWith('password')
})
controller
const bcrypt = require('bcrypt')
async function hashPassword(password) {
return await bcrypt.hash(password, 12)
}
function test() {
return 'not mocked'
}
module.exports = { hashPassword, test }
model
const mongoose = require('mongoose')
const { isEmail } = require('validator')
const { hashPassword } = require('../controllers/authController')
const userSchema = mongoose.Schema({
name: {
type: String,
unique: true,
required: [true, 'Username required'],
minLength: [4, 'Username should have at least 4 characters'],
},
email: {
type: String,
unique: true,
sparse: true, //ignore documents without the email field when determining uniqueness
validate: [isEmail, 'Please enter a valid email.'],
},
password: {
type: String,
required: [true, 'Password required'],
minLength: [6, 'Password should have at least 6 characters'],
},
confirmPassword: {
type: String,
required: true,
validate: [matches, 'The passwords do not match'],
},
hashedPassword: {
type: String,
select: false,
},
})
function matches() {
const { password, confirmPassword } = this
return password === confirmPassword
}
userSchema.post('validate', async function (doc) {
//hash password
this.hashedPassword = await hashPassword(this.password)
//remove plain password before saving
doc.password = undefined
doc.confirmPassword = undefined
})
const User = mongoose.model('User', userSchema)
module.exports = User
I am guessing the model file isn't using the mock version of hashPassword for some reason. However, if I simply use mock, it works as expected.
const request = require('supertest')
const app = require('../src/app.js')
const db = require('./db.js')()
const authController = require('../src/controllers/authController')
//in memory database
beforeAll(async () => await db.connect())
afterEach(async () => await db.clear())
afterAll(async () => await db.disconnect())
jest.mock('../src/controllers/authController')
it('hashes the password', async () => {
console.log(require('../src/controllers/authController'))
const user = await request(app)
.post('/auth/signup')
.send({ name: 'User', password: 'password', confirmPassword: 'password' })
console.log(user.body)
expect(authController.hashPassword).toHaveBeenCalled()
})
(Is it relevant that it says "[Function: hashPassword]" instead of "[Function: mockConstructor]")
logged controller
(no hashed password, meaning the model is using the mocked version of the function)
logged response
I am confused by why jest.mock works while jest.spyOn doesn't. I found many similar questions, but I haven't really found an answer.

Mongoose validation error, "email is not defined"

I am new to mongoose and express. I try to create a simple login backend, however when send a post request with
{
"userEmail": "abc#xyz", "password": "pswrd"
}
I get "email is not defined" error whose type is "VALIDATION". My User Schema is as follows:
const mongoose = require("mongoose");
const bcrypt = require("bcrypt");
const UserSchema = new mongoose.Schema({
email: {
type: String,
required: [true, "Email is required"],
trim: true,
unique: true,
},
password: {
type: String,
trim: true,
required: [true, "Password is required"],
},
username: {
type: String,
required: [true, "Username is required"],
trim: true,
unique: true,
},
});
UserSchema.pre("save", async function (next) {
const user = await User.findOne({ email: this.email });
if (user) {
next(new Error(`${this.email} already taken`));
return;
}
const user1 = await User.findOne({ username: this.username });
if (user1) {
next(new Error(`${this.username} already taken`));
return;
}
const salt = await bcrypt.genSalt(8);
this.password = await bcrypt.hash(this.password, salt);
next();
});
// userSchema.statics is accessible by model
UserSchema.statics.findByCredentials = async (email, password) => {
const user = await User.findOne({ email });
if (!user) {
throw Error("User does not exist.");
}
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
throw Error("Unable to login");
}
return user;
};
const User = mongoose.model("User", UserSchema);
module.exports = User;
I use findByCredentials to check if the User is in my mongoDB database or not. Finally, my login.js is as follows:
const express = require("express");
const mongoose = require("mongoose");
const User = require("../db/models/User");
const loginRouter = express.Router();
loginRouter.get("/api/login2", (req, res) => res.send("In Login"));
loginRouter.post("/api/login", async (req, res) => {
const { userEmail, password} = req.body;
if (!validateReqBody(userEmail, password)) {
return res
.status(401)
.send({ status: false, type: "INVALID", error: "invalid request body" });
}
try {
const newUser = new User({
email: userEmail,
password: password,
});
await newUser.findByCredentials(email, password);
} catch (error) {
const validationErr = getErrors(error);
console.log(validationErr);
return res
.status(401)
.send({ status: false, type: "VALIDATION", error: validationErr });
}
res.send({ status: true });
});
//user.find --> mongoose documentation
// Validates request body
const validateReqBody = (...req) => {
for (r of req) {
if (!r || r.trim().length == 0) {
return false;
}
}
return true;
};
// Checks errors returning from DB
const getErrors = (error) => {
if (error instanceof mongoose.Error.ValidationError) {
let validationErr = "";
for (field in error.errors) {
validationErr += `${field} `;
}
return validationErr.substring(0, validationErr.length - 1);
}
return error.message;
};
module.exports = { loginRouter };
Thank you.
You need to use body-parser middleware in backend
const bodyParser = require('body-parser');
const express = require('express');
const app = express();
//bodypraser middleware
app.use(bodyParser.json());
You can read more about bodyparser here
Happened to me once, it was really annoying. I don't know If it would help you, but try sending the post request with headers: { 'Content-Type': 'application/json' }, using fetch.
Definition of findByCredentials() is in User model. I was trying to reach that function by the object instance newUser that i created in login.js. However, i should have called the function as User.findByCredentials(email, password).

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.

Form Validations Node.js

How to Check "invalid numbers" Validations in mongoose? Is there any special method or keyword available?
Model.js given below?
Model.js
const Schema = mongoose.Schema;
const userSchema = new Schema({
Username: {
type: String,
required: true
},
Password: {
type: String,
required: true
}
});
userSchema.pre('save', async function (next) {
try {
const user = this;
if (!user.isModified('Password')) {
next();
}
const salt = await bcrypt.genSalt(10);
const passwordHash = await bcrypt.hash(this.Password, salt);
this.Password = passwordHash;
next();
} catch (error) {
next(error);
}
});
module.exports = User;
You can do it custom validators of mongoose: https://mongoosejs.com/docs/validation.html#custom-validators
For example, if you want your password to contain only numbers(using RegEx):
const Schema = mongoose.Schema;
const userSchema = new Schema({
Username: {
type: String,
required: true
},
Password: {
type: String,
required: true,
validate: {
validator: function(v) {
return /^[0-9]*$/gm.test(v); // this is RegEx of validation only numbers : https://www.regextester.com/21
},
message: props => `${props.value} should be a number!`
},
}
});
userSchema.pre('save', async function (next) {
try {
const user = this;
if (!user.isModified('Password')) {
next();
}
const salt = await bcrypt.genSalt(10);
const passwordHash = await bcrypt.hash(this.Password, salt);
this.Password = passwordHash;
next();
} catch (error) {
next(error);
}
});
module.exports = User;```

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

Resources