how to prevent mongoose save() from creating some schema default values? - node.js

I have this schema that have an expiry as a counter measure from felling data base with invalidated users so if the user doesn't do a certain validation his account will be deleted !! if he validates then the expiration gets removed, every thing is ok until now but if the user changes his email (after being activated ) what happens in that user.save() for some reason creates the expiration object again on the already existing model in DB even if the user has already been validated ! why does changing email causing this creation to happened ? I did another actions only the email change is creating this expires I tried to prevent it from happening by setting the expiration to null when changing email but it looks too awkward.
again thanks for the effort.
const Userschema = new mongoose.Schema(
{
User: "",
Name: "",
Email: {
type: 'string',
require: [true, 'Please provide an Email'],
unique: true,
lowercase: true,
validate: [validator.isEmail, 'please provide a valid email ']
}// date the the user has been created !!!
createdAt: {
type: Date,
default: Date.now()
},// exparation is by defult
expiration: {
//unique: true,
type: Date,
default: Date.now(),
expires: 60,
},
}
);
// removing experation logic if the user does a valid validation !
const update2 = await User.findOneAndUpdate(
{ Email: req.body.Email }, {
$unset: {
'expiration': 'expires'
}
})
// Solution but not clean.
const user = await User.findById(req.body.id)
console.log(user, "useeeeeeeeeeeeeeeer");
user.Email = newEmail
user.expiration = ''
user.save()

const user = await User.findOneAndUpdate(
{
'_id': req.body.id,
},
{
'$set': {
Email: newEmail
}
}, { _id: true, new: true }
)
This solved the issue still what is the diffrent between User.findById and using save() , and using find one and update.

Related

Log a user in and get their profile

I am attempting to log a user in to my DB. When I log the user in, it returns the first userId in the DB and not the user who logged in. I have been struggling with this for a while and really am at a dead end.
This is my POST route to log the user in:
// login
router.post("/login", async (req, res) => {
const user = await User.findOne({
email: req.body.email,
});
const secret = process.env.SECRET;
if (!user) {
return res.status(400).send("the user not found!");
}
if (user && bcrypt.compareSync(req.body.password, user.passwordHash)) {
const token = jwt.sign(
{
userId: user.id,
isAdmin: user.isAdmin,
},
secret,
{ expiresIn: "1d" }
);
res.status(200).send({ user: user.email, token: token });
} else {
res.status(400).send("password is wrong!");
}
});
The const user = await User.findOne({ email: req.body.email, }); this returns the wrong user.
When I query the endpoint get a users profile with the userId it gets the right information. So its got nothing to do with the DB.
This is the call in the app.
const handleSubmit = () => {
axios
.post(`${baseURL}users/login`, {
email: email,
passwordHash: password,
})
.then(res => {
console.log('USER ID TOKEN', res.data.token);
setbearerToken(res.data.token);
AsyncStorage.setItem('bearerToken', res.data.token);
const decoded = decode(res.data.token);
setTokenID(decoded.userId);
dispatch(setUser(res.data));
});
};
user.js model
const userSchema = mongoose.Schema({
contactName: {
type: String,
required: true,
minlength: 5,
maxlength: 50
},
phone: {
type: String,
required: true,
minlength: 5,
maxlength: 50
},
passwordHash: {
type: String,
required: true,
minlength: 5,
maxlength: 1024
},
token: {
type: String,
},
isAdmin: {
type: Boolean,
default: false
},
clubName: {
type: String,
required: true,
},
clubAddress: {
type: String,
required: true,
},
clubEmail: {
type: String,
required: true,
},
clubPhone: {
type: String,
required: true,
},
clubWebsite: {
type: String,
required: true,
},
clubContact: {
type: String,
required: true,
},
})
Your schema doesn't have a field email to filter on.
const user = await User.findOne({
email: req.body.email,
});
Maybe you try clubEmail field. I reproduced the behavior and it looks like that mongoose ignores the filter if the field does not exist in the Schema an just returns the first document in the collection.
E.g.
const userSchema = new Schema(
{
name: String,
age: Number
}
)
const User = mongoose.model('User', userSchema);
User.findOne({name: "Superman"}, ...
Returns the user with name "Superman".
const userSchema = new Schema(
{
name: String,
age: Number
}
)
const User = mongoose.model('User', userSchema);
User.findOne({xname: "Superman"}, ...
But when using xname in the filter document which does not exist in my schema neither in the collection as field the query returns the first document in my test collection (its not Superman).
Also look here similar issue: Model.find Mongoose 6.012 always return all documents even though having filter
Issue reported: https://github.com/Automattic/mongoose/issues/10763
Migration Guide to Mongoose 6:
https://mongoosejs.com/docs/migrating_to_6.html#strictquery-is-removed-and-replaced-by-strict

User Roles and rules(permission) access in node.js

I created a node.js application (Bus-ticket-booking app). MongoDB is the database system I'm using. I haven't yet finished the front end. I'm doing API queries with Postman.
For authentication, I'm using JWT. Now I want to add roles and rules for users such as the app's administrator, supervisor, and normal user.
1 -> A user can have many roles assigned to them (admin, supervisor).
2 -> Permissions can be assigned to a role ( Create, Update, delete etc...).
As a result, a user can have one or more roles, and each role can have one or more permissions. A user can use APIs for which he has rights, such as creating data, deleting data, updating data, and so on.
Here is the user schema:
const userSchema = new mongoose.Schema({
firstname: {
type: String,
required: true,
},
lastname: {
type: String,
required: true,
},
email: {
type: String,
unique: true,
required: true,
validate(value) {
if (!validator.isEmail(value)) {
throw new Error("Please provide the valid email address");
}
},
},
password: {
type: String,
required: true,
trim: true,
minLength: 8,
},
phone: {
type: Number,
required: true,
unique: true
},
tokens:[{
token: {
type: String,
required:true
}
}]
},{
timestamps:true
});
I'm new to it and have very little knowledge about it.
Is there anyone who can assist me?
If you just need a couple different roles,
I suggest you go with Sajawal Hassan's concept of simply adding a boolean field to determine user's access level.
However, if you are planning to create where there are multitude of roles to be added, and do not want field to be added for each role:
Add permissions array field to data model and create a permission list (to better organize) to the user data model
set up a middleware to add to your routers
set up groups to allow the user of your routers, and pass them through routers
1a. I suggest you create a list of roles within the user model file. Possibly a dictionary.
.../models/user.js
const ROLES = {
ADMIN: "ADMIN",
SUPERVISOR: "SUPERVISOR"
}
...
module.exports = mongoose.model('User', UserSchema);
module.exports.ROLES = ROLES;
1b. add a array field to Users models which will have the roles as permissions
.../user.js
const userSchema = new mongoose.Schema({
...,
permissions: [String],
...
});
Set up a middleware or in this case you already have auth; add functional capabilities to the function in which it will check its parameter or attach it to options (if you are checking for token, or other auth params)
.../auth.js
module.exports = function (options) {
...
// get user, validate token b4 here
user.permissions.forEach(permission => {
if (options.allowedGroup.indexOf(permission)){
// if authenticated
return next();
}
}
// could not authenticate at this point
return next(errorHandler) // throw a error or handle it way you want
}
3a. set up groups to determine which roles will have access to each router or a set of routers
.../routes/api_123.js
const mongoose = require('mongoose');
const User = mongoose.model('User');
const readGroup = [User.ROLES.SUPERVISOR, User.ROLES.ADMIN];
const writeGroup = [User.ROLES.ADMIN];
3b. pass the group you made as allowedGroup in param of middleware and set it up with a asyncHandler
...
const asyncHandler = require('express-async-handler');
...
router.get('/user/:id', auth({allowedGroup: readGroup}), asyncHandler(async(req, res, next) => {
... // your stuff here
res.status(200).json(data);
}))
router.post('/user/:id', auth({allowedGroup: writeGroup}), asyncHandler(async(req, res, next) => {
... // your stuff here
res.status(200).json(data);
}))
You should try to watch a full course on express and mongodb but you would have to add fields in the user schema that specifies if the user has permissions i.e admin: { type: booleen, default: false } then set the booleen to true if you want the user to be admin then create a route for something only admin sould be able to do lets say to delete a user so then in there check if the admin field in user schema is true. If so then user can delete otherwise throw err. it would be really helpful if you were to provide code snippets and other helpful links learn more here: https://stackoverflow.com/help/how-to-ask
edit:
Do keep in mind im using mongodb atlas for the code
Add an admin field (or any role that you want im gonna go with admin here)
so change
const userSchema = new mongoose.Schema({
firstname: {
type: String,
required: true,
},
lastname: {
type: String,
required: true,
},
email: {
type: String,
unique: true,
required: true,
validate(value) {
if (!validator.isEmail(value)) {
throw new Error("Please provide the valid email address");
}
},
},
password: {
type: String,
required: true,
trim: true,
minLength: 8,
},
phone: {
type: Number,
required: true,
unique: true
},
tokens:[{
token: {
type: String,
required:true
}
}]
},{
timestamps:true
});
to this
const userSchema = new mongoose.Schema({
admin: {
type: Booleen,
default: false,
},
firstname: {
type: String,
required: true,
},
lastname: {
type: String,
required: true,
},
email: {
type: String,
unique: true,
required: true,
validate(value) {
if (!validator.isEmail(value)) {
throw new Error("Please provide the valid email address");
}
},
},
password: {
type: String,
required: true,
trim: true,
minLength: 8,
},
phone: {
type: Number,
required: true,
unique: true
},
tokens:[{
token: {
type: String,
required:true
}
}]
},{
timestamps:true
});
I just added the admin field in the user schema
Then lets say you only want the admin to be able to delete users
for that you would have to create a route like this
router.delete("/delete/:id", async (req, res) => {
try {
// First find the user admin wants to delete
const user = await User.findById(req.params.id) // getting id from the id you put in url
// Make sure the user who wants to delete another user is an admin
if (user.admin) {
await user.deleteOne() // This deletes the user
} else {
res.status(403).json("You are not allowed to do this action!")
}
} catch (error) {
res.sendStatus(500);
}
});

Mongoose - Is it possible to access schema properties inside a mutation?

Newbie here. I'm trying to create a movie recommendation app and I've been struggling with this authorization problem for a while now. My goal is to have a single user schema with a Boolean 'isAdmin' property to differentiate between ordinary users and admin users. The problem is that when I attempt to query the isAdmin variable in the mutation to ensure that the logged in user passed in from the context has the necessary privileges to perform the operation, I get undefined.
User Schema
const userSchema = new mongoose.Schema({
username: {
type: String,
index: {
unique: true
}
},
email: {
type: String,
required: true,
index: {
unique: true
}
},
password: {
type: String,
required: true
},
contributions: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Movie'
},
isAdmin: {
type: Boolean,
required: true,
default: false
}
})
newMovie mutation
newMovie: async (parent, args, { models, user }) => {
if (!user){
throw new AuthenticationError('You must be signed in to submit a new movie')
}
console.log(user.isAdmin)
if (user && user.isAdmin === false) {
throw new ForbiddenError('You are not qualified')
}
return await models.Movie.create({
title: args.title,
year: args.year,
addedBy: mongoose.Types.ObjectId(user.id)
})
}
I attempted to console log user.isAdmin to see the value but instead I'm getting undefined. I have also tried using enum values 'user and admin' for a property 'roles' with the same result.
After struggling for hours, I realized that loading the user from the context returns an object with only the ObjectId and iat which I believe is the object's date of creation. To access the rest of the properties of the context user, I had to search for the user in models and assign that to a variable from which I can access the rest of the properties.
newMovie: async (parent, args, { models, user }) => {
if(!user){
throw new AuthenticationError('You must be logged in')
}
active = await models.User.findById(user.id)
if(active && active.role !== "ADMIN"){
throw new ForbiddenError('Only users can leave reviews')
}
return await models.Movie.create({
title: args.title,
year: args.year,
addedBy: mongoose.Types.ObjectId(active.id)
})
This was the working solution for this problem.

How to insert a user's id into a separate collection in Mongodb?

I have two models, one that holds the user and one that holds their text(Blog). I simply want to append the user's id every time they post their text so I can later go ahead and query for my own use.
This is what I have tried doing but nothing happens. Do we need to use req.body.user_id? Why is req.session.user not working(not being added along with the new instance of Blog on save) when I intentionally made it carry the user's id
router.route("/blog/add").post((req, res) => {
// Retrieve the uid form the user
// Save uid to the db along with the whole Blog instance
let blog = new Blog({
user_blog: req.body.user_blog,
createdAt: req.body.createdAt,
date: req.body.date,
user_id: req.session.user //Not working even though it holds the id already
});
blog.save()
.then(blog => {
res.status(200).json({
message: "Blog saved succeccfully"
});
})
.catch(err => {
res.status(400).send("Failed to save users blog");
});
});
Schema Blog
let Blog = new Schema({
user_blog: {
type: String,
required: true
},
createdAt: {
type: Date,
default: Date.now
},
date: {
type: String,
default: moment(new Date()).format("MMM Do YY, HH:mm")
},
user_id: {
type: mongoose.Schema.Types.ObjectId // This will be the users own id
}
}, { collection: "users_blogs" });
The user_id is not being appended, why?

Mongoose UPDATE function not working

I am working on the user accounts part of my project. I successfully completed the the GET, POST and DELETE methods, now I am missing the PUT.
When the user submits the update user form, the req.body will be something like this
{ user:
{ firstName: 'asas',
lastName: 'assa',
studentId: '1234',
email: 'as#as.as',
password: '123'
}
}
My schema looks like this:
const userSchema = new Schema({
cuid: { type: 'String', required: true },
firstName: { type: 'String', required: true },
lastName: { type: 'String', required: true },
studentId: { type: 'Number', required: true },
password: { type: 'String', required: true },
email: { type: 'String', required: true },
dateAdded: { type: 'Date', default: Date.now, required: true },
lastLogin: { type: 'Date', default: null, required: false },
});
and finally my update function looks like this.
export function updateUser(req, res) {
console.log(req.body)
firstName = sanitizeHtml(req.body.user.firstName );
lastName = sanitizeHtml(req.body.user.lastName);
studentId = sanitizeHtml(req.body.user.studentId);
email = sanitizeHtml(req.body.user.email);
password = sha512(req.body.user.password).toString('hex');
let update = { firstName, lastName, studentId, email, password };
User.findOneAndUpdate(req.params.cuid,update,function(err,updated){
if(error){
return res.status(500).send(err);
}else{
return res.json({ user: updated });
}
});
}
I can't figure out why my put method is not working, maybe a second pair of eyes can see the flaw.
You're close. The problem is that you aren't passing the id properly. MongoDB is looking for an identifier in the form of an object. You've written:
User.findOneAndUpdate(req.params.cuid,update,function(err,updated){
What you need to do is separate your arguments into separate objects:
User.findOneAndUpdate({ _id: req.params.cuid }, { $set: update }, function(err, updated) {
Additionally, you need to use $set, otherwise you'll overwrite the whole Object. $set tells Mongo to only update the fields specified via the update object, which you defined a couple lines above.
you are not using the correct structure of findOneAndUpdate. first you have define , on which basis search the id.
export function updateUser(req, res) {
console.log(req.body)
firstName = sanitizeHtml(req.body.user.firstName );
lastName = sanitizeHtml(req.body.user.lastName);
studentId = sanitizeHtml(req.body.user.studentId);
email = sanitizeHtml(req.body.user.email);
password = sha512(req.body.user.password).toString('hex');
let update = { firstName, lastName, studentId, email, password };
User.findOneAndUpdate({_id: req.params.cuid}, {$set: updated}, function (err, user) {//correct structure
if(error){
return res.status(500).send(err);
}else{
return res.json({ user: updated });
}
});
}
hope this helps.
I believe you need to make the update parameter as an object.
The first parameter is a query object. For example {firstName : "whatever"}.
The second parameter contains the updates.
As an example, if you are trying to update an user last name, and searching it by the name, you should have something like
findOneAndUpdate({firstName:req.body.firstName},{lastName:req.body.lastName},function...})
I believe you are trying to search by the id, so you should put something like
findOneAndUpdate({cuid:req.body.cuid},{$set:{firstName:req.body.firstName,studentId:req.body.studentId...}),function...})
I hope my answer was helpful for you.

Resources