Node, Mongoose - Referencing Document not working as expected - node.js

In my app, users can create posts. I want to assign a user to each post by referencing the users ObjectId.
Post model and schema:
const postSchema = new mongoose.Schema({
postImage: {
type: String,
required: true
},
caption: {
type: String
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
date: {
type: Date,
default: Date.now()
}
})
const Post = mongoose.model('Post', postSchema)
User model and Schema:
const userSchema = new mongoose.Schema({
username: {
type: String,
required: true,
unique: true,
minlength: 4,
maxlength: 50
},
password: {
type: String,
required: true,
minlength: 6,
maxlength: 75
}
})
userSchema.methods.generateAuthToken = () => {
const token = jwt.sign({ _id: this._id }, config.get('jwtPrivateKey'))
return token
}
const User = mongoose.model('User', userSchema)
In my post route, I apply the following middleware (auth middleware):
module.exports = (req, res, next) => {
const token = req.header('x-auth-token')
if (!token) return res.status(401).send('No token provided')
try {
const decoded = jwt.verify(token, config.get('jwtPrivateKey'))
req.user = decoded
next()
}
catch (ex) {
res.status(400).send('Invalid token')
}
}
Post route:
router.post('/', [auth, upload.single('postImage')], async (req, res) => {
const post = new Post({
postImage: `http://localhost:3000/${req.file.path}`,
caption: req.body.caption,
user: req.user._id
})
await post.save()
res.send(post)
})
When I make a request to the route, I set x-auth-token, and I get a response of the post object created. However in the database the user property on the post object has not been set . Only the following properties have been set on the post object created. _id, date, postImage, caption, __v. Does anybody know why the user is not being set as a property? Thanks.
UPDATE:
This is how I generate the jwt token, with _id attached:
userSchema.methods.generateAuthToken = () => {
const token = jwt.sign({ _id: this._id }, config.get('jwtPrivateKey'))
return token
}

Related

How can I fix : "Cannot read properties of undefined( reading 'role')" I believe its a mongodb/mongoose issue. How can I fix database request issue?

I am currently getting problems reading role on my postman POST request on backend nodejs express. I was wondering how would i go about fixing this?
below is the route for authentication in my middleware/auth.js
exports.isAuthenticatedUser = catchAsyncErrors(async (req, res, next) => {
const { token } = req.cookies
if (!token) {
return next(new ErrorHandler('Login first to access this resource.', 401))
}
const decoded = jwt.verify(token, process.env.JWT_SECRET)
req.user = await User.findById(decoded.id);
next()
})
// Handling users roles
exports.authorizeRoles = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return next(
new ErrorHandler(`Role (${req.user.role}) is not allowed to acccess this resource`, 403))
}
next()
}
}
I have added in extra information. Hopefully this helps clear up some missing background info. But the ideas is to show you where ```decoded.id`` comes from (getJwtToken). which by the way is used in the registration process of user accounts. which I also share at the bottom.
addition to models/users.js :
const mongoose = require('mongoose');
const validator = require('validator');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken')
const crypto = require('crypto')
const userSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Please enter your name'],
maxLength: [30, 'Your name cannot exceed 30 characters']
},
email: {
type: String,
required: [true, 'Please enter your email'],
unique: true,
validate: [validator.isEmail, 'Please enter valid email address']
},
password: {
type: String,
required: [true, 'Please enter your password'],
minlength: [6, 'Your password must be longer than 6 characters'],
select: false
},
avatar: {
public_id: {
type: String,
required: true
},
url: {
type: String,
required: true
}
},
role: {
type: String,
default: 'user'
},
createdAt: {
type: Date,
default: Date.now
},
resetPasswordToken: String,
resetPasswordExpire: Date
})
userSchema.methods.getJwtToken = function () {
return jwt.sign({ id: this._id }, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRES_TIME
});
}
controllers/authController.js
exports.registerUser = catchAsyncErrors(async (req, res, next) => {
const result = await cloudinary.v2.uploader.upload(req.body.avatar, {
folder: 'avatars',
width: 150,
crop: "scale"
})
const { name, email, password } = req.body;
const user = await User.create({
name,
email,
password,
avatar: {
public_id: result.public_id,
url: result.secure_url
}
})
sendToken(user, 200, res)
})
and your utils/jwtToken.js
const sendToken = (user, statusCode, res) => {
// Create Jwt token
const token = user.getJwtToken();
// Options for cookie
const options = {
expires: new Date(
Date.now() + process.env.COOKIE_EXPIRES_TIME * 24 * 60 * 60 * 1000
),
httpOnly: true
}
res.status(statusCode).cookie('token', token, options).json({
success: true,
token,
user
})
}
module.exports = sendToken;
The error is due to your user to be undefined. You should handle that case, but if you don't want for any reason you can just check it is not null using req.user?.role:
exports.authorizeRoles = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user?.role)) {
return next(
new ErrorHandler(`Role (${req.user?.role}) is not allowed to acccess this resource`, 403))
}
next()
}
}
In this case, if the user is not defined the role will be undefined too (returning the same error of Role (${req.user.role}) is not allowed to acccess this resource)
NULL CHECK USER
exports.authorizeRoles = (...roles) => {
if (!req.user) //throw error (user doesn't exist in db)
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return next(
new ErrorHandler(`Role (${req.user.role}) is not allowed to acccess this resource`, 403))
}
next()
}
}

Circular Dependency Error for deleteMany MongoDB

I am writing the Model for my Web App API, and am getting the following circular dependency error:
Warning: Accessing non-existent property 'deleteMany' of module exports inside circular dependency
(Use node --trace-warnings ... to show where the warning was created)
.
Here is my code:
const validator = require('validator')
const bcrypt = require('bcrypt')
const jwt = require('jsonwebtoken')
const Task = require('./task')
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const userSchema = new Schema({
email: {
type: String,
unique: true,
required: true,
trim: true,
lowercase: true,
validate(value) {
if (!validator.isEmail(value)) {
throw new Error('Email is invalid.')
}
}
},
password: {
type: String,
required: true,
trim: true,
minLength: 8
},
name: {
type: String,
unique: true,
required: true,
trim: true
},
tokens: [{
token: {
type: String,
required: true
}
}]
})
userSchema.pre('save', async function(next) {
const user = this
if (user.isModified('password')) {
user.password = await bcrypt.hash(user.password, 8)
}
next() // run the save() method
})
userSchema.pre('deleteOne', {document: true, query: false}, async function(next) {
const user = this
await Task.deleteMany({owner: user._id})
next()
})
userSchema.methods.toJSON = function() {
const user = this
const userObject = user.toObject()
delete userObject.password
delete userObject.__v
delete userObject.tokens
return userObject
}
userSchema.methods.generateAuthToken = async function () {
const user = this
const token = jwt.sign({ _id: user._id.toString() }, process.env.JSON_WEB_TOKEN_SECRET)
user.tokens = user.tokens.concat({ token })
await user.save()
return token
}
userSchema.statics.findByCredentials = async (email, password) => {
const user = await User.findOne({email})
if (!user) {
throw new Error('Unable to login')
}
const isMatch = await bcrypt.compare(password, user.password)
if (!isMatch) {
throw new Error('Unable to login')
}
return user
}
userSchema.virtual('tasks', {
localField: '_id',
foreignField: 'owner',
ref: 'Task'
})
const User = mongoose.model('User', userSchema);
module.exports = User
Any idea what could be going wrong? I have checked my Node.js and MongoDB versions and updated them, but continue to get this same error when I try to delete. I can provide further details of my code if necessary. The problem area in question is the one leading with userScheme.pre('deleteOne'....

Trying to get current logged in user tasks in Nodejs and MongoDB

I'm working on a project that enables the admin to assign tasks to different users, every users should only see his own tasks.
I tried doing that by using the user.id as key, when the user logs in we send a token, and that token includes the user_id and other user info, I'm trying to extract the id from that token and view tasks based on that.
Tasks Model
const TaksSchema = new Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'users',
},
taskName: {
name: String,
},
taskDesc: {
name: String,
},
dateAssigned: {
type: Date,
default: Date.now,
},
requiredDate: {
type: Date,
},
completed: { type: Boolean, default: false },
});
// Export Schema
module.exports = Tasks = mongoose.model('tasks', TaksSchema);
User model
const UserSchema = new Schema({
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
role: {
type: String,
enum: ['basic', 'admin'],
default: 'basic',
},
avatar: {
type: String,
},
date: {
type: Date,
default: Date.now,
},
});
// Export Schema
module.exports = User = mongoose.model('users', UserSchema);
tasks route
router.get('/', (req, res) => {
const errors = {};
Tasks.findOne({ user: req.user.id })
.populate('user', ['name', 'avatar'])
.then((task) => {
if (!task) {
errors.notask = "There's no Tasks Right Now";
return res.status(400).json(errors);
}
res.json(task).catch((err) => res.status(404).json(err));
});
});
When I try sending the get request from postman I get this error
TypeError: Cannot read property 'id' of undefined
For security I'm sending the Id through JWT token.
Here is the code
const payload = { id: user.id, name: user.name, avatar: user.avatar }; // Create jwt patload
// Sign the token
jwt.sign(
payload,
keys.secretOrKey,
{ expiresIn: 3600 },
(err, token) => {
res.json({ sucess: true, token: 'Bearer ' + token });
}
);
You must first verify/decode the ID in order to receive the payload.
In the code, you are trying to access the id field from user. Actually you need to add a middleware which validates the JWT and appends the result in the user field.
Example :
middlewares/validateJwt.js
Assuming you are sending JWT in the header as Bearer Token.
try{
let token = req.headers.authorization.split(" ")[1]; // Bearer <token>
let result = jwt.verify(token, "JWT_SECRET", options);
req.user = result;
next();
} catch...
Your API should look like
GET /api//users/:id
In that case you can use
req.params.id
as a first argument.
Pranesh A S's answer should get you going. To complete his suggestion, if you store the middleware validateJWT.js as
const jwt = require("jsonwebtoken");
module.exports = function (req, res, next) {
const token = req.header("x-auth-token");
if (!token) {
res.status(401).json({ msg: "NO token. Auth Failed" });
}
try {
console.log(token);
const decoded = jwt.verify(token, "Secret key");
req.user = decoded.user;
next();
} catch (err) {
console.log(err);
res.status(401).json({ msg: "Token is not valid" });
}
};
import it in your App.js file and while defining routes,
use
const validateJWT =require ("./middleware/validateJWT")
and
app.get("/", validateJWT, (req, res)=>{
// do stuff
})

how to return the user email

I have created post route to store posts in the database. It's a protected route so user can store post only after entering the login details. When I post in postman, I've seen that the user email is not returned in the object. Even in the mongodb collection, I don't see the email associated with the post. How do I include the email as well with the post object. I don't want the user to enter the email again and again when posting because they have already logged in. So I kinda want to store the email automatically with the post. Hope I make sense. Can someone help me with this?
Right now the object is kinda stored like this in the posts collection in mongodb
_id: ObjectId("5f1a99d3ea3ac2afe5"),
text: "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. ",
user:ObjectId("5f1a99d3eac2c82afe5"),
age:20,
country:"India",
gender:"male",
date:2020-07-24T08:23:35.349+00:00,
__v:0
I want the email too in the above object.
Post model
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const PostSchema = new Schema ({
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
text: {
type: String,
required: true
},
name: {
type: String
},
email: {
type: String
}
,
age: {
type: Number,
required: true
},
gender: {
type: String,
required: true
},
country: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
})
module.exports = Post = mongoose.model('post', PostSchema)
post route
const express = require('express');
const router = express.Router();
const auth = require('../../middleware/auth')
const { check, validationResult} = require('express-validator');
const User = require('../../models/User')
const Post = require('../../models/Post')
router.post('/', [auth, [
check('text', 'Text is required').not().isEmpty()
]], async (req,res)=>{
const errors = validationResult(req);
if(!errors.isEmpty()){
return res.status(400).json({errors: errors.array()})
}
try {
const user = await (await User.findById(req.user.id)).isSelected('-password')
const newPost = new Post({
text: req.body.text,
name: user.name,
user: req.user.id,
age: req.body.age,
country: req.body.country,
gender: req.body.gender,
email: req.user.email // this email is not stored with the post and I want this to be automatically posted in the collection without the user having to type it again to save the post
})
const post = await newPost.save();
res.json(post);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error')
}
})
module.exports = router;
User model
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
})
module.exports = User = mongoose.model('user', UserSchema);
Change isSelected to select
const user = await (await User.findById(req.user.id)).isSelected(password')
What I potentially see the problem here is, once you have grabed the object of user, you're still referring to req.user.email instead of user.email.
If that does not solve your problem, try to console.log the user returned from after User.findById
Update:
You can see here that isSelected returns boolean. So you're essentialy getting true for having password field in user. Also instead of req.user.email use user.email

Trying to add new user to database (MongoDB) getting this error

I am trying to save a user to MongoDB database using post request as follow, but I got the error TypeError: User is not a function. I can't figure out anything wrong with it. The output says that error is present on the line " const user = new User(req.body);"
postman output nodejs output
is my userschema wrong or the export method is wrong.
const User = require("../models/user");
exports.signup = (req, res) => {
const user = new User(req.body);
user.save((err, user) => {
if (err) {
return res.status(400).json({
err: "NOT able to save user in DB"
});
}
res.json({
name: user.name,
email: user.email,
id: user._id
});
});
};
exports.signout = (req, res) => {
res.json({
message: "User signout"
});
};
//user schema
var mongoose = require("mongoose");
const crypto = require('crypto');
const uuidv1 = require('uuid/v1');
var userSchema = new mongoose.Schema({
name:{
type:String,
required:true,
maxlenght:32,
trim: true
},
lastname:{
type: String,
maxlenght:32,
trim: true
},
email:{
type: String,
trim: true,
required: true,
unique:true
},
userinfo:{
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(function(password){
this._password = password;
this.salt = uuidv1();
this.encry_password = this.securePassword(password);
})
.get(function(){
return this._password;
})
userSchema.methods = {
authenticate: function(plainpassword){
return this.securePassword(plainpassword) === this.encry_password;
},
securePassword: function (plainpassword){
if(!plainpassword) return "";
try {
return crypto.createHmac('sha256',this.salt)
.update(plainpassword)
.digest('hex');
} catch (err) {
return "";
}
}
};
module.export = mongoose.model("User",userSchema)
Here is the issue with the code.
Line 1: const user = new User(req.body);
Line 2: user.save((err, user) => {
JS is now confused about user which is a constant variable to that of the user which is the return value of the save action.
So, to get rid of this, rename the return value of save action to something else like responseUserObj. Thus your above two lines of code should now be
const user = new User(req.body);
user.save((err, responseUserObj) => {
Happy coding.

Resources