Trying to get current logged in user tasks in Nodejs and MongoDB - node.js

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

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

How to get logged in user? req.user is undefind

How to get logged in user in express app. I want to know witch user create post. This is my Post.js model:
const postsSchema = mongoose.Schema(
{
user: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User',
},
title: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
image: {
type: String,
required: true,
},
category: {
type: String,
required: true,
},
numLikes: {
type: Number,
required: true,
default: 0,
},
comments: [commentSchema],
},
{
timestamps: true,
}
);
This is my authUser function where i log in user with email and password:
const authUser = async (req, res) => {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (user && (await user.matchPassword(password))) {
res.json({
_id: user._id,
name: user.name,
email: user.email,
isAdmin: user.isAdmin,
token: generateToken(user._id),
});
} else {
res.status(401);
throw new Error('Invalid email or password');
}
};
generateToken function is JWT:
import jwt from 'jsonwebtoken';
const generateToken = id => {
return jwt.sign({ id }, 'abc123', {
expiresIn: '30d',
});
};
export default generateToken;
When i create post i want to know user who created it, this is my create post function:
const createPost = async (req, res) => {
const post = new Post({
user: req.user._id,
title: 'Sample Title',
description: 'Sample Desc',
image: '/images/sample.jpeg',
category: 'Sample Category',
numLikes: 0,
});
const createPost = await post.save();
res.status(201).json(createPost);
};
When i try to create post i got this error in console:
UnhandledPromiseRejectionWarning: TypeError: Cannot read property '_id' of undefined.
I can log in with postman, i can register, i can get user by id. How to tell my app Hey i am logged in user and have access to req.user object
You need to have the client send the token back to you, which you then validate (typically via a middleware affecting some section of endpoints so you don't have to call a validation function in individual endpoints).
If instead, express is also your front end, then you need to use a library like express-session https://www.npmjs.com/package/express-session to manage cookies. A good example is available on their page:
// Use the session middleware
app.use(session({ secret: 'keyboard cat', cookie: { maxAge: 60000 }}))
// Access the session as req.session
app.get('/', function(req, res, next) {
if (req.session.views) {
req.session.views++
res.setHeader('Content-Type', 'text/html')
res.write('<p>views: ' + req.session.views + '</p>')
res.write('<p>expires in: ' + (req.session.cookie.maxAge / 1000) + 's</p>')
res.end()
} else {
req.session.views = 1
res.end('welcome to the session demo. refresh!')
}
})
Otherwise you've sent the token to client and done nothing with it.
Do you need of a middleware like this:
module.exports = (req, res, next) => {
// Authorization token example: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWQiOiIxIiwiaWF0IjoxNTE2MjM5MDIyfQ.dYo0kOIhum5mMTRV8CAn8gQ_6aqoDQLE--vCZD4E-fg
const { authorization } = req.headers
if (!authorization) return res.send({ message: 'Token not provided', code: 400 })
const [ schema, token ] = authorization.split(' ')
if (schema !== 'Bearer') return res.send({ message: 'Token is bad formated', code: 400 })
if (!token) return res.send({ message: 'Invalid token', code: 400})
jwt.verify(token, 'abc123', (error, decoded) => {
if (error) return res.send({ message: error.message, code: 401})
req.userId = decoded.id
})
next()
}
Hope this is helpful for you.
The first thing you should do is to send token back to the client or attach cookies to your response.
After which you set up a middleware that will check cookies or token in your case using jwt.verify(token, jwtSecret). That will return the id and all other things you stored in the token, then you then store them in req.user, where you will be able to access the details later.
//if you stored token in cookies -
const {accessToken} = req.signedCookies
const payload = isTokenValid(accessToken) //verify the token
req.user = payload.user;
return next();
//if you stored in auth header
const bearerToken = req.headers.authorization
//bearerToken = "Bearer token"
const token = bearerToken.split(" ").join(",")[1]
//verify the token using jwt
const payload = isTokenValid(token)
req.user = payload
return next()

Node, Mongoose - Referencing Document not working as expected

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
}

Unable to reference from token schema token mongodb express passport

i need help with this problem been dealing it for days.
i am trying to make a verification email route by using passport to hash passwords while issuing a verification token to the user.
here is my code for index.js in controllers folder
const User = require("../models/user");
const Token = require("../models/token")
const crypto = require("crypto");
const nodemailer = require("nodemailer");
var smtpTransport = nodemailer.createTransport({
service: 'Gmail',
auth: {
user: process.env.GMAILUSER,
pass: process.env.GMAILPW
}
});
module.exports = {
async postRegister(req, res, next) {
var user = new User({
name: req.body.name,
email: req.body.email,
isVerified: false,
username: req.body.username
});
await User.register(user, req.body.password);
res.redirect('/');
var token = new Token({ _userId: user._id, token: crypto.randomBytes(16).toString('hex') });
token.save(function (err) {
if (err) { return res.status(500).send({ msg: err.message
});
}
var mailOptions = {
to: user.email,
from: 'xxxt#xxx.com',
subject: 'xxxxx verify email',
text:'You are receiving this because you need to verify your email for your account.\n\n' +
'Please click on the following link, or paste this into your browser to complete the process:\n\n' +
'http://' + req.headers.host + '/confirmation/' + token.token + '\n\n' +
'If you did not request this, please ignore this email.\n'
};
smtpTransport.sendMail(mailOptions, function(err) {
if (err) { return res.status(500).send({ msg: err.message }); }
res.status(200).send('A verification email has been sent to ' + user.email + '.');
});
})
},
confirmationPost(req,res, next) {
Token.findOne({ token: req.params.token }, function (err, token) {
if (!token)
{console.log("sss")
} else {
User.findOne({ _id: token._userId, email: req.body.email }, function (err, user) {
if (!user) return console.log(user)
if (user.isVerified) return res.status(400).send({ type: 'already-verified', msg: 'This user has already been verified.' });
user.isVerified = true;
user.save(function (err) {
if (err) { return res.status(500).send({ msg: err.message }); }
res.status(200).send("The account has been verified. Please log in.");
})
});
};
})
}
}
This is my Token Schema
const mongoose = require('mongoose');
const passportLocalMongoose = require('passport-local-mongoose');
const Schema = mongoose.Schema;
const tokenSchema = new mongoose.Schema({
_userId: {
type: Schema.Types.ObjectId,
ref: 'User' },
token: {
type: String,
required: true },
createdAt: {
type: Date, required: true,
default: Date.now, expires: 43200 }
});
tokenSchema.plugin(passportLocalMongoose);
module.exports = mongoose.model('Token', tokenSchema);
lastly my user schema
const mongoose = require('mongoose');
const passportLocalMongoose = require('passport-local-mongoose');
const Schema = mongoose.Schema;
const UserSchema = new Schema({
username: String,
name: String,
email: { type: String, unique: true },
image: String,
isVerified: { type: Boolean, default: false },
password: String,
passwordResetToken: String,
passwordResetExpires: Date,
posts: [
{
type: Schema.Types.ObjectId,
ref: 'Post'
}
]
});
UserSchema.plugin(passportLocalMongoose);
module.exports = mongoose.model('User', UserSchema);
everything works fine until the part where the email verification was sent to my email and when i clicked on the link. It gives an error, i tried to console.log
and found that this line from controllers folder index.js
confirmationPost(req,res, next) {
Token.findOne({ token: req.params.token }, function (err, token) {
if (!token)
{console.log("err")
} else {
User.findOne({ _id: token._userId, email: req.body.email }, function (err, user) {
gives me back null.
how do i link that current line to get the token from the registered user?
i've used postman to send a get request to the confirmation route while giving it back the same token and it works.

Saving ref to a User login

I'm having a trouble to write a simple User and Tweet Schemas relation.
I created a middleware that will check a user token once he logged in. This is a token based authentication. The problem lies in the '/tweet' route section of how to save a tweet to a Logged in user.
schema.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var UserSchema = new Schema({
name: String,
username: { type: String, required: true, index: { unique: true }},
password: { type: String, required: true, select: false },
level: String,
tweet: [{
type: Schema.Types.ObjectId, ref: 'Tweet'
}],
});
var TweetSchema = new Schema({
_creator: { type: Number, ref: 'User'},
title: String,
content: String
});
module.exports = mongoose.model('User', UserSchema);
module.exports = mongoose.model('Tweet', TweetSchema);
api.js
let just assume that I have written '/login' route for log in a user and create a token above this middleware.
// The middleware for verifying user's token ( The user already login )
apiRouter.use(function(req, res, next) {
// do logging
console.log("Somebody just came to our app!");
var token = req.body.token || req.param('token') || req.headers['x-access-token'];
// check if token exist
if(token) {
jwt.verify(token, superSecret, function(err, decoded) {
if(err) {
res.status(403).send({ success: false, message: 'Failed to authenticate user' });
} else {
// if everything is good save request for use in other routes
req.decoded = decoded;
next();
}
});
} else {
res.status(403).send({ success: false, message: 'No token provided' });
}
});
// another route for creating a tweet, in order to use this route, user's token must be verified.
apiRouter.route('/tweet')
.post(function(req, res) {
// The problem is I dont know how to get a user that just Login to create a tweet that will save in his user_tweet Schema.
var tweet = new Tweet({
_creator: ???,
title: req.body.title,
content: req.body.content
});
tweet.save(function(err) {
if(err) res.send(err);
res.json({ message: "New tweet has been added" });
});
});
Currently I'm having a trouble to create a tweet and saves it to a User that already log in and token has been verified. What should I write in '/tweet' route section to achieve my goal.
I'm trying to emulate a Twitter app for you guys information.
I don't know what you're using to generate your tokens, but the standard practice is to encode the user id as part of the token. So when you decode the token, like you've done here:
req.decoded = decoded;
req.decoded it should contain the user id, which you can then use to look up the user and check their permissions, or in your case, you'd use it to create your tweet object.
So something like this:
// create a json webtoken
var token = jwt.sign({
id = user._id,
name: user.name,
username: user.username
}, superSecret, {
expiresInMinute: 1440
});
Then do:
var tweet = new Tweet({
_creator: req.decoded.id,
title: req.body.title,
content: req.body.content
});

Resources