mongoose schema method returning undefined - javascript

I want to create a method that validates the user's password by using bcrypt.compare()
here is the code below.
UserSchema.methods.validatePassword = async (data) => {
console.log(this.email); // returns undefined
console.log(this.first_name); // returns undefined
return await bcrypt.compare(data, this.password);
};
here is the UserSchema I created
const UserSchema = mongoose.Schema(
{
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
},
{ timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' } }
);
when getting this.password in my schema .pre('save', ..) it works but shows undefined when I use schema methods. :(
here is the implementation of the method
const verifySignIn = async (req, res, next) => {
const { email, password } = req.body;
try {
const user = await User.findOne({ email });
if (!user) {
return res.status(404).json({
status: 'failed',
message: 'User Not found.',
});
}
const isValid = await user.validatePassword(password);
if (!isValid) {
return res.status(401).send({
message: 'Invalid Password!',
data: {
user: null,
},
});
}
next();
} catch (err) {
Server.serverError(res, err);
}
};

In the guide it says:
Do not declare methods using ES6 arrow functions (=>). Arrow functions explicitly prevent binding this, so your method will not have access to the document ...
So in this case, you just need to change UserSchema.methods.validatePassword = async (data) => {... to UserSchema.methods.validatePassword = async function(data) {...

Related

._doc in Mongoose

I have this js code
app.post('/auth', async (req, res) => {
try {
const user = UserModel.findOne({email: req.body.email}).exec()
if (!user) return res.status(404).json({
message: 'Not find user'
})
const isValidPassword = bcrypt.compare(req.body.password,user._doc.passwordHash)
if (!isValidPassword) return res.status(404).json({
message: 'Incorrect password'
})
}
catch (err) {
console.log(err)
res.status(500).json({
message: 'error'
})
}
})
And I have this Schema
import mongoose from 'mongoose'
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
surname: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
passwordHash: {
type: String,
required: true
},
telegramUrl: {
type: String,
required: true
},
avatarUrl: String
},
{
timestamps: true
}
)
export default mongoose.model('User', userSchema)
In this line
const isValidPassword = bcrypt.compare(req.body.password,user._doc.passwordHash)
I have error: Cannot read properties of undefined (reading 'passwordHash'). Why am I getting an error? He writes to me that ._doc undefined but why? Help me please
use these two methods in your schema
const bcrypt = require("bcrypt");
// Create Hash Salt Password ..
userSchema.pre("save", async function (next) {
if (!this.isModified("passwordHash")) return next();
this.passwordHash = await bcrypt.hash(this.passwordHash, 12);
next();
});
// Compare Password ...
userSchema.methods.comparePassword = function (passwordHash) {
return bcrypt.compareSync(passwordHash, this.passwordHash);
};
And in your auth code
app.post('/auth', async (req, res) => {
try {
const user = await UserModel.findOne({email: req.body.email}).exec()
if (user) {
res.status(400).json(
{ message: 'User already register'})
}
else{
const newuser = New User({
name: req.body.name,
// also write other schema fields
}
const res = await newuser.save();
console.log(res)
catch (err) {
console.log(err)
res.status(500).json({
message: 'error'
})
}
})
I hope this should resolve your problem

Struggling to patch a record in mongodb, unique not working and req.body undefined

I'm learning node.js and it's amazing, especially with mongo, but sometimes I struggle to solve a simple problem, like patching only 1 attribute in my user database.
It's easier to patch something that cannot be unique, but I want to patch an username attribute and I defined it as "unique" in my schema. I don't know why, but MongoDB doesn't care other db entry has the same user, it let me save.
My schema:
/** #format */
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const userSchema = new Schema(
{
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
email: { type: String, required: true, unique: true },
userNumber: { type: Number, required: true },
description: { type: String },
verified: { type: Boolean, default: false },
isAdmin: { type: Boolean, default: false },
isSubscriber: { type: Boolean, default: false },
isDisabled: { type: Boolean, default: false },
acceptedTerms: { type: Number, required: true },
},
{ timestamps: true }
);
module.exports = mongoose.model('User', userSchema);
On my user controllers in node, I want to updateOne({ _id: userId}, { username: myNewUsername} but it always happens, it doesn't take into consideration another db entry can have the username, so I tried a different strategy but it doesn't work:
exports.changeUsername = (req, res, next) => {
// Requirements
const userId = req.params.userId;
const newUsername = req.body.username;
console.log('userId: ' + userId);
console.log('newUsername: ' + req.body.username);
User.findOne({ username: req.body.username })
.then(result => {
console.log(result);
if (result.username) {
const error = new Error('Could not find this sport');
error.code = 'DUPLICATED';
throw error;
}
return;
})
.catch(err => next(err));
// if no username was in use then updateOne
User.updateOne({ _id: userId }, { username: newUsername })
.then(result => {
res.status(200).json({
message: 'username has been updated',
username: result.username,
});
})
.catch(err => next(err));
};
I don't know if I can updateOne at the same time add some find validation. What I am doing wrong? Users cannot have the same username.
On the console, it seems it works, but it throws an extra error I don't understand:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at new NodeError (node:internal/errors:371:5)
at ServerResponse.setHeader (node:_http_outgoing:576:11)
at ServerResponse.header (/Users/username/Sites/pipi-api/node_modules/express/lib/response.js:776:10)
I tried this other approach and it works, but doesn't trigger an error if the record is not unique as I stated in the schema.
// GET ONLY ONE SPORT BY ID
exports.changeUsername = async (req, res, next) => {
// Requirements
const userId = req.params.userId;
const newUsername = req.body.username;
console.log('userId: ' + userId);
console.log('newUsername: ' + req.body.username);
try {
const oldUsername = await User.findOne({ username: newUsername });
if (oldUsername.username) {
throw new Error('Error: its duplicated');
}
const user = await User.findOneAndUpdate(
{ _id: userId },
{ username: newUsername },
{ new: true }
);
console.log('User successfully updated.');
return res.status(200).json({ success: true, user });
} catch (err) {
console.log('ERROR: ', err);
return res.status(400).json({ success: false });
}
};
If I uncomment the code above, it triggers an error if I find a record on the database that matches but it doesn't allow me to continue to my next line of codes I the username is not found on the db.
I get a new error:
userId: 6231bdef334afbde85ed9f43
newUsername: tetete
ERROR: TypeError: Cannot read properties of null (reading 'username')
at exports.changeUsername (/Users/user/Sites/pipi-api/v1/controllers/users/index.js:43:21)
That error is not related to Mongo. It means that you are trying to send a response and the response is already sent.
The issue is because you called both User.findOne and User.updateOne and both of them has .then handler. So the first one of these that finishes will send the actual response. In the moment the second one finished, the response is already send and the error is thrown because you are trying to send response again.
Mongo will throw the error if you try to change username property that some other user already have. You should check if the req.params.userId and req.body.username sent correctly to the backend. Try to console.log() them and check if they are maybe null.
Consider refactoring your handler to use async/await instead of then/catch. You can do it like this:
exports.changeUsername = async (req, res, next) => {
try {
const userId = req.params.userId;
const newUsername = req.body.username;
const user = await User.findOneAndUpdate({ _id: userId }, { username: newUsername }, { new: true });
console.log('User successfully updated.');
return res.status(200).json({ success: true, user });
} catch (error) {
console.log('ERROR: ', error);
return res.status(400).json({ success: false });
}
}

Check if ID exist in related mongodb collection with mongoose

I have a "Drinkers" model and a "Sodas" model which is "related" - a drinker can have drunk X amount of sodas.
The route to get the data is this
router.get('/all/:drinkerId', sodasController.getAllSodasFromDrinker)
In my sodasController, is there a way to check if :drinkerId exists in the "Drinkers" collection and if not return an error that the drinker doesn't exist, without having to require the drinkersController in the sodasController.
Right now getAllSodasFromDrinker looks like this
const Sodas = require("../models/sodas.model");
exports.getAllSodasFromDrinker = async (req, res, next) => {
try {
const id = req.params.drinkerId;
if (id.match(/^[0-9a-fA-F]{24}$/)) {
await Sodas.find({ drinker: id }).exec((err, drinkerItem) => {
if (err) {
return next(err);
}
res.json({ data: drinkerItem });
});
} else {
return next("ID is in the wrong format");
}
} catch (error) {
return next(error);
}
};
In that function, I want to check if a user exists with the applied ID.
I want to avoid having to
const Drinkers = require("../models/drinkers.model") in the sodasController
The Drinkers model:
const Schema = mongoose.Schema;
const drinkersSchema = new Schema(
{
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
sodas: {
type: Schema.Types.ObjectId,
ref: "Sodas",
},
},
{ timestamps: true }
);
The Sodas model
const Schema = mongoose.Schema;
const sodaSchema = new Schema(
{
name: {
type: String,
required: true,
},
drinker: {
type: Schema.Types.ObjectId,
ref: "Drinkers",
},
},
{ timestamps: true }
);
I would add a middleware function which validates if the drinkerId exists. If it exists, you can continue to the controller. If not, then you should throw a 404 error.
Your route:
router.get(
'/all/:drinkerId',
drinkerMiddleware.exists,
sodasController.getAllSodasFromDrinker
);
drinkerMiddleware:
exports.exists = async (req, res, next) => {
try {
const drinker await Drinker.find({ drinker: req.params.drinkerId }).exec();
if (!drinker) {
return next("Drinker not found.");
}
return next();
} catch (error) {
return next(error);
}
};

Why do I get a 400 error when logging a user using bcrypt?

I'm trying to create login authentication, but I keep getting an 400 error in Postman saying that my syntax is bad when testing things out. I'm pretty sure my entire User model is solid, but for good measure, I've attached the whole thing in case something's off there. Otherwise, I'm really not sure what the problem is or where to go from here.
This is the data that I'm sending that triggers the 400 Bad Request (the request cannot be fulfilled due to bad syntax) and logs the invalid password to the console:
{
"email": "andrew#example.com",
"password": "Red12345!"
}
Here's my entire user model code:
const mongoose = require('mongoose')
const validator = require('validator')
const bcrypt = require('bcryptjs')
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true
},
email: {
type: String,
unique: true,
require: true,
trim: true,
lowercase: true,
validate(value) {
if(!validator.isEmail(value)) {
throw new Error('Email is invalid')
}
}
},
age: {
type: Number,
default: 0,
validate(value) {
if(value < 0) {
throw new Error('Age must be a positive number.')
}
}
},
password: {
type: String,
trim: true,
lowercase: true,
required: true,
minlength: 7,
validate(value) {
if(value.toLowerCase().includes("password")) {
throw new Error("Password can't be 'password'.")
}
}
}
})
userSchema.statics.findByCredentials = async (email, password) => {
const user = await User.findOne({ email })
if (!user) {
throw new Error('User not found')
}
const isMatch = await bcrypt.compare(password, user.password)
if (!isMatch) {
throw new Error('Invalid password')
}
return user
}
//Hash the plain text password before saving
userSchema.pre('save', async function (next) {
const user = this
if(user.isModified('password')) {
user.password = await bcrypt.hash(user.password, 8)
}
next()
})
const User = mongoose.model('User', userSchema)
module.exports = User
And here's the user login router:
router.post('/users/login', async (req, res) => {
try {
const user = await User.findByCredentials(req.body.email, req.body.password)
res.send(user)
} catch (e) {
console.log(e.message)
res.status(400).send()
}
})

sequelize.js custom validator, check for unique username / password

Imagine I have defined the following custom validator function:
isUnique: function () { // This works as expected
throw new Error({error:[{message:'Email address already in use!'}]});
}
However, when I attempt to query the DB I run into problems:
isUnique: function (email) { // This doesn't work
var User = seqeulize.import('/path/to/user/model');
User.find({where:{email: email}})
.success(function () { // This gets called
throw new Error({error:[{message:'Email address already in use!'}]}); // But this isn't triggering a validation error.
});
}
How can I query the ORM in a custom validator and trigger a validation error based on the response from the ORM?
You can verify if the email already exists like that:
email: {
type: Sequelize.STRING,
allowNull: false,
validate: {
isEmail:true
},
unique: {
args: true,
msg: 'Email address already in use!'
}
}
Here's a simplified sample of a functioning isUnique validation callback (works as of SequelizeJS v2.0.0). I added comments to explain the important bits:
var UserModel = sequelize.define('User', {
id: {
type: Sequelize.INTEGER(11).UNSIGNED,
autoIncrement: true,
primaryKey: true
},
email: {
type: Sequelize.STRING,
validate: {
isUnique: function(value, next) {
UserModel.find({
where: {email: value},
attributes: ['id']
})
.done(function(error, user) {
if (error)
// Some unexpected error occured with the find method.
return next(error);
if (user)
// We found a user with this email address.
// Pass the error to the next method.
return next('Email address already in use!');
// If we got this far, the email address hasn't been used yet.
// Call next with no arguments when validation is successful.
next();
});
}
}
}
});
module.exports = UserModel;
With Sequelize 2.0, you need to catch Validation Errors.
First, define the User Model with a custom validator:
var User = sequelize.define('User',
{
email: {
type: Sequelize.STRING,
allowNull: false,
unique: true,
validate: {
isUnique: function (value, next) {
var self = this;
User.find({where: {email: value}})
.then(function (user) {
// reject if a different user wants to use the same email
if (user && self.id !== user.id) {
return next('Email already in use!');
}
return next();
})
.catch(function (err) {
return next(err);
});
}
}
},
other_field: Sequelize.STRING
});
module.exports = User;
Then, in the controller, catch any Validation Errors:
var Sequelize = require('sequelize'),
_ = require('lodash'),
User = require('./path/to/User.model');
exports.create = function (req, res) {
var allowedKeys = ['email', 'other_field'];
var attributes = _.pick(req.body, allowedKeys);
User.create(attributes)
.then(function (user) {
res.json(user);
})
.catch(Sequelize.ValidationError, function (err) {
// respond with validation errors
return res.status(422).send(err.errors);
})
.catch(function (err) {
// every other error
return res.status(400).send({
message: err.message
});
});
Success callback is called even if no user is found. You have to check if the function passes a user as an argument:
isUnique: function (email) {
var User = seqeulize.import('/path/to/user/model');
User.find({where:{email: email}})
.success(function (u) { // This gets called
if(u){
throw new Error({error:[{message:'Email address already in use!'}]}); // But this isn't triggering a validation error.
}
});
}
Define the User Model with a custom validator:
const { DataTypes } = require('sequelize');
const sequelize = require('../config/db');
const UserModel = sequelize.define('user', {
id: {
type: DataTypes.INTEGER(11).UNSIGNED,
autoIncrement: true,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
email: {
type: DataTypes.STRING,
allowNull: false,
validate: {
isUnique: (value, next) => {
UserModel.findAll({
where: { email: value },
attributes: ['id'],
})
.then((user) => {
if (user.length != 0)
next(new Error('Email address already in use!'));
next();
})
.catch((onError) => console.log(onError));
},
},
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
createdAt: {
type: DataTypes.DATE,
allowNull: false,
},
updatedAt: {
type: DataTypes.DATE,
allowNull: false,
},
});
module.exports = UserModel;

Categories

Resources