How next() works here? - node.js

i just do password encryption before saving it in db, i just want to know how next() works here?
i know that next() helps us to jump one middleware to next middleware.
userModel.js
....
....
//below will encrypt password before saving user in db
//password is the field of documents og mongodb
userSchema.pre("save", async function () {
console.log(this.password);
if (!this.isModified("password")) {
next();
}
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
});
....
....
//full code userModel.js
const mongoose=require("mongoose");
const bcrypt=require("bcryptjs");
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique:true },
password: { type: String, required: true },
pic: {
type: String,
default:
"https://icon-library.com/images/anonymous-avatar-icon/anonymous-avatar-icon-25.jpg",
},
},
{
timestamps:true,
});
//below compare given password with before password
userSchema.methods.matchPassword=async function(enteredPassword){
console.log("this.password",this.password)
return await bcrypt.compare(enteredPassword,this.password)
}
//below will encrypt password before saving user in db
userSchema.pre("save", async function () {
console.log(this.password);
if (!this.isModified("password")) {
next();
}
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
});
//mongoose.model(<Collectionname>, <CollectionSchema>)
//generally const "User" and "User" in mongoose.model() written in same name
const User=new mongoose.model("User",userSchema);
module.exports=User;
I am new in node js please help me to understand middleware

You can see more about middleware in mongoose here:
How mongoose middleware works and what is next()?
In short, next() will tell mongoose you are done and to continue with the next step in the execution chain. In your example, it will execute the "save" command to save the password to database if the password is not modified and go to next callback (if has).

Related

Why is the reference not being saved along with the rest of the data?

I am new with MongoDB "relations" and I am trying to save data to a MongoDB database. There are two models, one model is the user and the other model is the authentication data. The data is saved correctly.
import { Schema, model } from 'mongoose'
const stringRequired = {
type: String,
trim: true,
required: true
}
const stringUnique = {
...stringRequired,
unique: true
}
const UserSchema = new Schema({
name: stringRequired,
username: stringUnique,
email: stringUnique,
}, { timestamps: true });
const AuthSchema = new Schema({
email: { type: Schema.Types.ObjectId, ref: 'User' },
salt: stringRequired,
hash: stringRequired,
}, { timestamps: true })
export const userModel = model('User', UserSchema)
export const authModel = model('Auth', AuthSchema)
As you can see, one of the models is referenced by another. The email field has a reference to the user, email being the id that I want to use for authentication. But for some reason, when I save the documents, all the data is sent except the reference.
This is my controller, which as you can see, abstracts the user and the authentication to carry out the business logic and then save it in the database separately.
function add(body: any) {
return new Promise((resolve, reject) => {
if (!body) {
const error = new Error('No body on the request')
reject(error)
} else {
const user = {
username: body.username,
email: body.email,
name: body.name
}
const saltRouds = 10
const salt = bcrypt.genSaltSync(saltRouds)
const hash = bcrypt.hashSync(body.password, salt)
const auth = { salt, hash }
store.add(userModel, user)
store.add(authModel, auth)
resolve('User created')
}
})
}
This is the store.add function.
async function add (collection: any, data: any) {
return await collection.create(data)
}
Note this code is writhed with Typescript.
You're missing the reference key when creating the Auth instance. The "foreign key" in MongoDB is the id of a document that has type Schema.Types.ObjectId and can be accessed with document._id.
So your code should look like:
const auth = { salt, hash };
const user = store.add(userModel, user);
auth.email = user._id;
store.add(authModel, auth);
Be aware that your store.add() function is an async function and you should wait for it's result like #jfriend00 said in the comments.
You can achieve that by making add() also an async funtion and doing:
const auth = { salt, hash };
const user = await store.add(userModel, user);
auth.email = user._id;
await store.add(authModel, auth);
or using the Promise approach by chaining .then(). You can read more about it here.

Unable to add properties in a MongoDB document

I am trying to implement password reset functionality in a MERN application. Whenever a user enters their email (for which they want to reset the password) and clicks on the "Send Password Reset Link" button, a POST request is made to the route "/account/forgot".
In the route handler function, I check whether any user with the given email exists or not. If a user exists, then I add resetPasswordLink and resetPasswordExpires properties to the user object and send a message "You have been emailed a password link" to the client.
The problem I am facing is I get the message at the frontend.
However, whenever I check the database, I don't see resetPasswordLink and resetPassworExpires properties being added to the user.
Where is the problem?
The code snippets are given below:
server/routes/passwordResetRoutes.js
const express = require("express");
const crypto = require("crypto");
const asyncHandler = require("express-async-handler");
const User = require("../models/userModel");
const router = express.Router();
router.post(
"/forgot",
asyncHandler(async (req, res, next) => {
const user = await User.findOne({ email: req.body.email });
if (user) {
user.passwordResetToken = crypto.randomBytes(20).toString("hex");
user.passwordResetExpires = Date.now() + 3600000;
await user.save();
res.json({
message: "You have been emailed a password reset link",
});
} else {
const err = new Error("No account with that email exists");
err.status = 404;
next(err);
}
})
);
module.exports = router;
server/models/userModel.js
const mongoose = require("mongoose");
const bcrypt = require("bcryptjs");
const userSchema = new mongoose.Schema({
firstName: {
type: String,
required: true,
},
lastName: {
type: String,
required: true,
},
email: {
type: String,
unique: true,
required: true,
},
password: {
type: String,
required: true,
},
resetPasswordToken: {
type: String,
},
resetPasswordExpires: {
type: Date,
},
});
userSchema.methods.matchPassword = async function (incomingPassword) {
return await bcrypt.compare(incomingPassword, this.password);
};
userSchema.pre("save", async function (next) {
if (!this.isModified("password")) {
next();
}
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
});
const User = mongoose.model("User", userSchema);
module.exports = User;
You're trying to update the passwordResetToken and passwordResetExpires fields but they are not present in the User model. That's why the user.save() call does nothing. They should be resetPasswordToken and resetPasswordExpires respectively.
user.resetPasswordToken = crypto.randomBytes(20).toString('hex')
user.resetPasswordExpires = Date.now() + 3600000
await user.save()

Bcrypt password fails when i try to compare?

I am a complete beginner in coding, currently learning NodeJs and i am stuck with this situation for days now.
I am trying to compare the hashed password in my mongodb with the users input through postman.
I am using bcrypt to compare the hashed password with the original string but i am getting false statement.
Any help is much appreciated
This is the mongoose model Schema,
const usersSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true,
},
email: {
type: String,
unique: true,
required: true,
lowercase: true,
validate(value) {
if (!validator.isEmail(value)) throw new Error("Invalid email");
},
},
password: {
type: String,
required: true,
trim: true,
minLength: 7,
validate(value) {
if (value.toLowerCase().includes("password")) {
throw new Error("Password should not consist of string 'password'.");
}
},
},
})
Right here I hash the password before saving to the database;
usersSchema.pre("save", async function (next) {
const user = this;
const saltRounds = 8;
if (user.isModified("password")) {
user.password = await bcrypt.hash(user.password, saltRounds);
}
next();
});
Below is the login route;
router.post("/users/login", async (req, res) => {
try {
const user = await Users.findByCredentials(
req.body.email,
req.body.password
);
res.send(user);
} catch (error) {
res.status(400).send(error);
}
});
Below is where I try to compare the passwords, help me figure out why I am getting false.
usersSchema.statics.findByCredentials = async (email, password) => {
const user = await Users.findOne({ email: email });
if (!user) {
throw new Error("Unable to log in!");
}
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
throw new Error("Unable to login");
}
return user;
};
when ever you use bcrypt it works with you password property on your model
In this case you dont want to trim or lower case any properties of password.
So when designing Schema we dont want to over-load password with extra validations.We have to work with minimal validations in order for bcrypt to function properly.I had the same issue i removedlowerCase:true,trim:true from the Schema then it worked.
try to remove extra validations or restriction from password other wise it will interfere with bcrypt compare function.
try using bcryptjs.hashSync()
and bcryptjs.compareSync instead

How to show Errors from Mongoose?

I have a user I can save in MongoDB, when I enter correct data, the save works.
But when I enter wrong data, I can't catch the errors to be seen for the user. All I can see is this on the code editor:
...UnhandledPromiseRejectionWarning: ValidationError: User validation
failed: username: username is not there!...
This error "kills" the server, and so I'm not rendering home-guest template.
The question is how I can catch the errors and show them to the user?
Schema:
const mongoose = require("mongoose")
const userSchema = new mongoose.Schema({
username: {
type: String,
required: [true, "username is not there!"],
minlength: 3,
maxlength: 20,
},
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
minlength: 6,
maxlength: 20,
},
})
module.exports = mongoose.model("User", userSchema)
Controller:
const mongoose = require("mongoose")
const userModel = require("../models/userModel")
exports.signUp = async (req, res) => {
const { username, email, password } = req.body
try {
const user = await new userModel({
username,
email,
password,
})
user.save()
} catch (error) {
res.render("home-guest", { error })
}
}
You just need to add an await to the save operation, since that's also async:
const mongoose = require("mongoose")
const userModel = require("../models/userModel")
exports.signUp = async (req, res) => {
const { username, email, password } = req.body
try {
const user = await new userModel({
username,
email,
password,
})
// Wait for the save to complete, also allowing you to catch errors
await user.save()
} catch (error) {
res.render("home-guest", { error })
}
}
EDIT: And note that you do not need an async in front of new userModel(). new cannot return a promise, it is always synchronous.

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()

Resources