I have a JSON web token that once it's verified gives me a string that is the object id value of a user I want to find but it seems that every time I try to query with it being parsed into a mongoose object ID it never find my user, am I parsing it right ? I always get a 401 invalid token paylaod
logRoute.get('/user', (req, res) => {
let token = req.body;
User.findOne({_id: mongoose.Types.ObjectId(jwt.verify(token.token, 'secretkey').subject)}, (error, user) => {
if (error) {
console.log(error)
} else {
if (!user) {
res.status(401).send('invalid token payload')
} else {
let userData = {firstname: user.firstname, lastname: user.lastname, type: user.type}
res.status(200).send(userData)
}
}
})
})
my user data
{
"_id": {
"$oid": "5efc7d60ba7a8d3db08ca767"
},
"type": "teacher",
"firstname": "arandomfirstname",
"lastname": "arandomlastname",
"login": "random1",
"pwd": "arandompassword"
}
and
console.log(jwt.verify(req.body.token, 'P3GPROJECT'))
return me
{ subject: '5efc7d60ba7a8d3db08ca767', iat: 1593690718 }
EDIT:
User model
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const userSchema = new Schema({
_id: String,
pwd: String,
lastname: String,
firstname: String,
type: String
});
module.exports = mongoose.model('user', userSchema, 'user');
You've to remove _id: String from model schema, since You've ObjectId as a value (not a string) and it makes mongoose to convert Your input to string, instead of ObjectId.
so:
const userSchema = new Schema({
_id: String,
pwd: String,
lastname: String,
firstname: String,
type: String
});
becomes:
const userSchema = new Schema({
pwd: String,
lastname: String,
firstname: String,
type: String
});
or You can keep it as string but have to define the logic of generation of proper _id:
const uuid = require('short-uuid');
const userSchema = new Schema({
_id: {
type: Schema.Types.String,
default: uuid.generate(), // this will generate unique string
},
pwd: String,
lastname: String,
firstname: String,
type: String
});
P.S. Keep in mind if You want to use _id: String, You've to remove old documents or convert them manually to be {_id: "id here"} instead of {_id: ObjectId("id here")}.
Bonus:
Also I recommend You to create middlewares and separate token handling, from user authorization check.
It will allow You to not repeat access token checking in every request and will help You to understand in which step You're doing it wrong.
And then to do what You want.
jwt.verify throws error if it's invalid, that's why I put try catch arount.
I'm extracting subject as userId and if it does not exist it also means that token invalid, otherwise I pass handling to next handler in routing.
authorizeUser gets req.accessToken.userId from previous middleware and tries to check if user exists. If yes - so it stores complete user object in req.user, otherwise will end with 401 also.
and at last route handling goes last step which does logic or it can be empty as res.status(200).send(req.user)
const checkAccessToken = (req, res, next) => {
try {
const {subject: userId} = jwt.verify(req.body.token, 'secretkey');
if (!userId) throw new Error('Token data does not contain user id');
req.accessToken = {userId};
next();
}
catch (error) {
console.error(error);
res.status(401).send({message: 'Invalid access token'});
}
};
const authorizeUser = async (req, res, next) => {
try {
const user =
await User.findById(req.accessToken.userId)
.select('_id firstname lastname type')
.lean();
if (!user) {
return res.status(401).send({message: 'Unauthorized'});
}
req.user = user;
req.user.id = req.accessToken.userId;
next();
}
catch (error) {
console.error(error);
res.status(500).end();
}
};
logRoute.get(
'/user',
checkAccessToken, // checking token and putting user id to `req.user.id`
authorizeUser, // checking if user exists in database and putting it as `req.user` object
async (req, res) => { // handling some logic or simply returning `req.user` object
// some extra logic here...
res.status(200).send(req.user);
}
);
Related
I'm trying to fetch only the users posts, and i'm using this code:
router.get("/:username", async (req, res) => {
try {
const user = await User.findOne({ username: req.params.username });
const posts = await Post.find({ userId: user._id });
res.status(200).json(posts);
} catch (err) {
res.status(500).json(err);
}
});
I'm using Postman for testing GET localhost:6000/posts/admin and every time I try it, it gives this error
"name": "CastError",
"message": "Cast to ObjectId failed for value \"admin\" (type string) at path \"_id\" for model \"Post\""
This is my posts collection in Monogodb
And this is the users collection in Mongodb
I don't know what i'm missing here, I just want the link posts/:username shows the posts of this username only
const user = await User.findOne({ username: req.params.username });
This query finds user but user._id comes as object but in your db userId saved as a string so you should probably save userId as object
const blogSchema = new Schema({
title: {
type: String,
required: true,
},
content: {
type: String,
required: true,
},
userId: { type: mongoose.Types.ObjectId, ref: 'user' }, //Example
});
Forgive me for my limited knowledge.
My problem is that I have made the code below myself. When it comes to validator email, address, phone, .., I don't know how to handle it. I have found some documents to read that I do not understand.Please help me!
Thanks very much!
userService.js
exports.findUserResgiter = async (email) => {
var result = null;
try{
result = await User.findOne({
email: email,
})
} catch(e){}
return result;
};
userModels.js
const UserShema = new Shema({
name: String,
date_of_birth: Date,
address: String,
phone: String,
email: String,
password: String
}, {
collection: 'users'
});
userController.js
exports.register = async (req, res, next)=>{
var email=req.body.email;
var password=md5(req.body.password)
var name=req.body.name
var address=req.body.address
var phone=req.body.phone
var date_of_birth=req.body.date_of_birth
var data = await userService.findUserResgiter(email);
console.log(data);
if(data === null){
res.status(200).json({message: "null"});
return User.create({
name: name,
email: email,
password: password,
address: address,
date_of_birth: date_of_birth,
phone: phone,
})
}else{
res.status(401).json({message: "This email already exists!"});
}
};
There are loads of libraries that can help you out with validation. Try something like Joi or Superstruct . Here's a basic example using Joi since it's quite popular:
I assume you're using express? So first I'm gonna write some middleware that checks the req.body against a schema:
export function validateResource(schema) {
return (req, res, next) => {
try {
schema.validate(req.body);
//move to next middleware if validation success
next();
}
catch(err) {
//pass to an error handling middleware
next(err);
//or send bad request status code
res.sendStatus(400);
}
}
Then define your schema:
const Joi = require('joi');
const userSchema = Joi.object({
name: Joi.string().required(),
email: Joi.string().email().required(),
password: Joi.string().required(),
//etc.... refer to the docs for other validation methods, there are loads
});
Use the middleware like so:
//require your userSchema
app.post("/some_endpoint", validateResource(userSchema), (req, res, next) => {
//at this point in the code, the data is validated
}
You can reuse the validateResource middleware with any schema you define.
so I have to Schemas. PostSchema and UserSchema
const mongoose = require("mongoose")
const PostSchema = new mongoose.Schema({
content: {
type: String,
required: true,
},
likes: {
type: Number,
required: true
},
rescreams: {
type: Number,
required: true
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
createdAt: {
type: Date,
default: Date.now
}
})
module.exports = mongoose.model("Post", PostSchema)
UserSchema:
const bcrypt = require("bcrypt");
const mongoose = require("mongoose");
const UserSchema = new mongoose.Schema({
userName: { type: String, unique: true },
email: { type: String, unique: true },
password: String,
});
// Password hash middleware.
UserSchema.pre("save", function save(next) {
const user = this;
if (!user.isModified("password")) {
return next();
}
bcrypt.genSalt(10, (err, salt) => {
if (err) {
return next(err);
}
bcrypt.hash(user.password, salt, (err, hash) => {
if (err) {
return next(err);
}
user.password = hash;
next();
});
});
});
// Helper method for validating user's password.
UserSchema.methods.comparePassword = function comparePassword(
candidatePassword,
cb
) {
bcrypt.compare(candidatePassword, this.password, (err, isMatch) => {
cb(err, isMatch);
});
};
module.exports = mongoose.model("User", UserSchema);
My question is: I'm trying to reference the User Object ID in the Post Schema. As you can see, I've done that with type: mongoose.Schema.Types.ObjectID. And I've seen this multiple times. But in my database, the User never shows up in the Document. What do I need to do?
Cheers
There is a difference between referencing a document and embedding a document.
If you want to store a document inside a document you should embed it, thus read operations will be faster because you won't need to perform JOIN operations.
Whereas referencing means storing an ID of an entity that you are referencing, when you need to access the document you are referencing, you need to fetch it from the collection by the ID you have stored. It is slower than embedding, but it gives you higher consistency and data integrity because the data is stored once at the collection and not duplicated at every object. And MongoDB does not support foreign keys so you should be careful with referencing.
So when you are storing the document using ref, you need to put an ObjectID as a user and then fetch the document you need to add populate call. e.g.
PostShema.findOne({ _id: SomeId }).populate('user');
try to save in a variable:
const UserId = UserSchema.Schema.Types.ObjectId;
for more information:
https://mongoosejs.com/docs/api/schema.html#schema_Schema.Types
I'm sorry, I'm having a hard time even formulating the question properly. Hopefully it's not too confusing.
I'm building a One To Many Relations in my Mongo DB Atlas. I'm using mongoose and Nodejs.
I'm trying to create a One User to Many Entries. For now let's just say it's a one to one, to remove a layer of complexity. One User To One Entry.
All the code in the backend works, but in short the issue I have is that.
Whenever I make a post request to create a new entry, I can include the user ID that the entry belongs to in the request. But whenever I make a post request to create a new user, I can't include an entry ID in the request, because no requests exist yet for that user. When I create a new entry, mongo db doesn't automatically update the document, to add that new entry to the user associated with it. And I don't know what I need to do on my end to get it to dynamically update the users to include new entries that belong to them.
Here are my models/schemas for users and entries, so you can see the association.
USER SCHEMA
const mongoose = require('mongoose');
const userSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
email: {type: String,
required: true,
unique: true,
displayName: String,
password: {type: String, required: true},
entry: {type: mongoose.Schema.Types.ObjectId, ref: 'Entry', required: true}
}, {collection: "users"});
module.exports = mongoose.model("User", userSchema);
ENTRY SCHEMA
const mongoose = require('mongoose');
const entrySchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
title: {type:String},
body: {type:String, required: true},
user: {type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true},
entryImage: {type: String}
}, {collection: 'entries'});
module.exports = mongoose.model('Entry', entrySchema);
Here are my routes for users and entries. You can see how I set up the logic for the associations
USER ROUTES
const express = require('express');
const router = express.Router();
const mongoose = require('mongoose');
const User = require('../models/user');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
router.get('/:userId', (req, res, next) => {
const id = req.params.userId;
User.findById(id)
.select("_id email displayName password entries")
.populate('entry')
.exec()
.then(user => {
res.status(200).json({
id: user._id,
email: user.email,
password: user.password,
entry: user.entry
})
})
.catch(err => {
error: err
})
})
router.post('/signup', (req, res, next) => {
User.find({email: req.body.email})
.exec()
.then(user => {
if(user.length >= 1){
return res.status(422).json({
message: "Username already exists!"
});
} else {
bcrypt.hash(req.body.password, 10, (err, hash) => {
if(err){
return res.status(500).json({
error: err
});
} else {
const user = new User({
_id: new mongoose.Types.ObjectId(),
email: req.body.email,
displayName: req.body.displayName,
password: hash
});
user.save()
.then(data => {
res.status(201).json({
message: "Your user information has been saved in our records",
id: data._id,
email: data.email,
displayName: data.displayName
})
})
.catch(err => {
res.status(500).json({
error: err
})
})
}
})
}
})
.catch(err => {
res.status(500).json({error : err})
})
}); //End of signup post request
EXAMPLE OF AN ENTRY POST REQUEST
EXAMPLE OF A USER POST REQUEST
Please let me know of you have any other questions. Thank you so much, in advance!
The problem is in your schema. You specified explicitly about the _id field.
Your current scheme does not allow mongoose to create this id automatically.
Well, there are two options:
Simplest way. Simply remove _id field from your schema. Mongoose will automatically generate this for you in every create request.
If you want to specify this, pass an option to mongoose so that it can auto-generate this for you
const userSchema = mongoose.Schema({
_id: { type: Schema.ObjectId, auto: true },
})
Been working on a project recently where users subscribe to a class... however I am having trouble with pushing the class to the array... here is my code
const userSchema = new mongoose.Schema({
email: String,
password: String,
secret: String,
classes: [String]
});
const userModel = mongoose.model("Class", userSchema)
app.post("/subscribe", function (req, res) {
const newClass = req.body.subClass;
const id = req.user.id
const ObjectId = mongoose.Types.ObjectId;
userModel.findOneAndUpdate(
{ _id: new ObjectId(id) },
{ $addToSet: { letters: [newClass] } },
{ upsert: false }
);
});
I skipped through the program for useful info so please comment if more code is needed... looking to finally finish this up!