What is the best way to update different properties? - node.js

I'm building an API with Restify which is based on Express. I'm using Typeorm and I'm wondering what is the best way to update different properties which came from user input.
Essentially I have a route like this:
server.put('/users/:id', errorHandler(update));
which fire this method:
const update = async (req: Request, res: Response) => {
const user = { ...req.body, id: req.params.id } as User;
res.send(await userService.update(user));
}
as you can see I used the spread operator to create an User entity. Then, inside userService.update I have the following:
export const update = async (user: User): Promise<User> => {
const repository = getRepository(User);
const entity = await repository.findOne({ id: user.id });
if (!entity) throw new errors.ResourceNotFoundError(`There is no user with id of ${user.id}`);
Object.assign(entity, user, { id: entity.id, chat_id: entity.chat_id, project_id: entity.project_id, deleted: false });
return await repository.save(entity);
}
as you can see, I want prevent that the data provided by the API consumer will replace some important properties like: id, chat_id, project_id, deleted, so I used the method Object.assign to achieve this.
Is this a good way? What do you suggest for improve this?

You can use update method of typeorm like this, it will partially update the values that you give as a second argument.
// this will find a user with id ${user.id} and will only
// change the fields that is specified in the user object
await repository.update(user.id, user);
// check if updated for debugging
const updatedUser = await repository.findOne(user.id);
console.log(updatedUser, null, 2)
If you want to create a new record of the existing user in db, then you only need to change it's id. To do that
Deep clone the object so there will be another user object with new reference
Remove id field from the deep cloned object and use insert afterwards
// Deep clone user object
const clonedUser = JSON.parse(JSON.stringify(user))
// Delete id field from the deep clone
delete clonedUser.id;
// create a new user with different id
await repository.insert(clonedUser);

You can filter your important properties.
And pass the user id to the update method of your userService.
const { id, chat_id, project_id, deleted, ...user } = req.body;
const { id } = req.params;
res.send(await userService.update(id, user));
This will make sure user object don't have the properties(that is important).
And you can change your update method like below:
export const update = (userId: string, user: User): Promise<User> => {
return getRepository(User).update(userId, user);
}

Related

how to stop users from viewing and updating another user's data in node.js?

I am storing a parking detail with a merchant id in the mongoose schema since a parking belongs to a certain merchant user and it cannot be empty or null.
Here is the model:
const parkingSchema = new mongoose.Schema({
merchantId: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: "Merchant",
},
//other details
})
merchant model is something like this:
const merchantSchema = new mongoose.Schema({
merchantId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Auth",
},
//other details
})
And finally the auth schema:
const authSchema = new mongoose.Schema({
accountType: {
type: String,
required: true,
trim: true,
default: "user",
enum: ["merchant", "user", "provider"],
},
//other details
})
If the original user wishes it, I simply want to update the parking data; otherwise, I want to throw an error.
I am using jsonwebtoken to authenticate users.
Here is the query to update the data:
exports.updateParking = async (req, res) => {
try {
const { parkingName, price, address, name, phoneNumber, about } = req.body;
const { parkingImage } = req.files;
const check_exist = await Auth.findById(req.data.id);
if (!check_exist) return res.status(404).json({ error: "User not found" });
console.log(req.data.id);
const updateData = await Parking.findByIdAndUpdate(
{ _id: req.params.id, merchantId: req.data.id }, // I think here is the problem
{
$set: {
parkingName,
price,
address,
...
},
}
);
return res.status(200).json({
success: true,
msg: "Parking has updated successfully",
});
} catch (error) {
return error.message;
}
};
However, the issue is that other users can now update another user's data which I want to stop
below is the query of middleware:
routing.patch("/parking/update/:id", middleware.authenticateToken, merchant.updateParking)
You should be showing each user only their parkings that they have created or belong to them.
const myParkings = async (req, res) => {
// always await code in try/catch block
const merchants = await Parkings.find({ user: req.user._id })
.. then populate the fields that you want to show
res.status(200).json({
success: true,
bookings,
});
};
you have to set this req.user._id when user logins. You could create a session.
I think what you're looking for is something like CASL Mongoose (or a similar package), and more specifically, the "conditions" section of the CASL docs.
What you're dealing with here is the distinction between 2 concepts:
AuthN (authentication) - determines who someone is and whether they are "authenticated" to make an API request
AuthZ (authorization) - determines what the authenticated user is allowed to do
In your app, middleware.authenticateToken is responsible for the AuthN piece of the equation. It makes sure that only users that have created an account are able to make requests to your API routes.
What you still need to solve for is the AuthZ piece, which can be done in a bunch of different ways, but one popular one is to use CASL, which is a Node AuthZ library that allows you to utilize your ORM's native query syntax to limit actions based on the authenticated (AuthN) user's attributes.
In other words, you can do something like, "Only allow user with ID 1 to update Parking entities that he/she owns". Below is generally what you're looking for (not tested for your use case, but the general idea is here):
const casl = require('#casl/ability');
// Define what a `Auth` (user) can do based on their database ID
function defineMerchantAbilities(merchantUser) {
const abilities = casl.defineAbility((allow, deny) => {
// Allow merchant to update a parking record that they own
allow('update', 'Parking', { merchantId: merchantUser.id })
})
return abilities
}
exports.updateParking = async (req, res) => {
const userId = req.data.id
const parkingId = req.params.id
// Find your merchant user in DB (see my comments at end of post)
const merchantUser = await Auth.findById(userId)
// Find your parking record
const parking = await Parking.findById(parkingId)
// Pass user to your ability function
const ability = defineMerchantAbilities(merchantUser)
// This will throw an error if a user who does not own this Parking record
// tries to update it
casl.ForbiddenError
.from(ability)
.throwUnlessCan('update', casl.subject('Parking', parking))
// If you make it here, you know this user is authorized to make the change
Parking.findByIdAndUpdate( ...your code here )
}
Additional comments/notes:
I would recommend removing your try/catch handler and using an Express default error handler as it will reduce the boilerplate you have to write for each route.
I would also recommend writing a middleware that finds a user by ID in the database and attaches it to a custom property called req.user so you always have req.user available to you in your authenticated routes.

Update record based on username given in Request body

I need to update value in Group db Group_name to the value send in Json payload.
Db schema
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
username: String,
Group_name: {
type: String,
default: '',
}
});
mongoose.model('User', UserSchema);
And API request
router.put('/join', async(req, res) => {
try {
const data = await User.updateOne(req.params.username, {
Group_name: req.body.Group_name
});
console.log(data)
res.send({ msg: "Group Updated!!!" })
} catch (err) {
console.error(err.message);
res.sendStatus(400).send('Server Error');
}
});
currently its updating only first record which is incorrect , my requirement is to check for all records based on username given and according to username given in request parameters ,i will update value of Group_name to the value sent in request body.
can anyone help me ?
Modify query condition.
const data = await User.updateOne(
{ username: req.params.username },
{ $set: { Group_name: req.body.Group_name } }
);
First of all, understand the difference between req.body & req.params
req.body means hidden parameters sent in request body like in post or put requests.
req.params means defined paramters in URL. For this, you must have it defined in your route like below
router.put('/join/:username', async (req, res) => {
// ^^^^^^^^ here it is defined, now you can access it like
const username = req.params.username;
//or
const {username} = req.params; // destructuring
}
there is one more thing and that is
req.query means undefined paramters attached to URL with ?/&
If you want to give username without pre defining like /join?username=john then use req.query
router.put('/join', async (req, res) => {
const {username} = req.query;
}
Then you should use updateMany() function instead of updateOne()
try {
const {username} = req.params;
const {Group_name} = req.body;
const data = await User.updateMany(
{username}, // find as many users where username matches
{Group_name} // update group name from body
);
console.log(data);
The consoled data would be like { n: 2, nModified: 2, ...} because the update queries don't return updated documents but status of the query. If you want to get updated record set, you have to query again with find().
// after update
const updatedRecord = await User.find({ username });
console.log(updatedRecord);
::POSTMAN::
Postman has two types of parameters
Params
Body
If you add in Params it will be added in URL /join?username=john#email.com&Group_name=GroupB and you have to access it in code with req.query.username or req.query.Group_name
If you add in Body it will be hidden and can be accessed with req.body.Group_name etc
Hope it helps!

How to get Json Data from MongoDB against a specific User who posted it?

router.post('/',auth, async (req, res) => {
const { error } = validate(req.body); //Error Check
if (error) return res.status(400).send(error.details[0].message);
let property = new Property({ //Creating Object: Property as per defined Schema:
title: req.body.title,
description: req.body.description,
price: req.body.price,
user: {
_id: req.user._id, //Getting the ID from auth middleware with JWT Token Authenticared
}
});
console.log({property});
await property.save(); //Saving the Object
res.send(property); //Displaying User with created Object i.e. Property
});
I'm Creating a Property using POST Method.
Now I want to Get the Data from MongoDB, but only for the user who's currently logged in and created that data.
//Writing a GET METHOD to List of Properties with Valid Token:
router.get('/', async (req,res)=>{
try{
//Getting the Information of User by the current user.id loggin in: ... .select('-password') sets it to don't show password
const property = await Property.findById(req.user );
res.send(property);
}
catch (ex){
console.error(ex.message);
}
});
Instead of findByIdtry find({user: req.user._id}).
req.user contains the authenticated user's information so by using _id we can get can the ID which helps us filter the Properties created by a Specific User

Prevent _id field from being supplied in a MongoDB query

I'm using Mongoose in NodeJS to control a MongoDB database.
I'm creating an API and for obvious security reasons, I want to prevent the auto generated document _id field from getting replaced by a manually generated one in the API request.
Schema:
{ name: String }
Creating a document:
const record = {
_id: '5e35517cc894c90327a34baf'
name: 'bob'
}
const insertRecords = async () => {
await Quiz.create(record);
};
insertRecords();
Results in the following document:
{
_id: '5e35517cc894c90327a34baf'
name: 'bob'
}
As can be seen, the _id supplied in the query, as long as it's a valid ObjectID, would replace the _id that was supposed to be auto generated by mongo.
Is there a way to check if this _id field is in the query so that I can reject the API request? The .create method triggers the pre save middleware hook which would always have the _id of the final document so I cannot depend on it to know whether the _id was in the query or it's the auto generated one.
The only option I found is to disable the _id field altogether but this does not make sense.
Solution #1 - Use .create() method with an explicit object.
It's actually easier than you think. This is self-explanatory - we only define what we want to allow. Mongoose will ignore anything that's not in the object.
const record = {
_id: '5e35517cc894c90327a34baf'
name: 'bob'
}
const insertRecords = async () => {
await Quiz.create({
name: record.name // only allow names.
});
};
insertRecords();
Solution #2 - Define a function to clear unwanted objects.
You can define a helper function to clear out unwanted fields.
const filterObj = (obj, ...allowedFields) => {
const newObject = {};
// If the current field is one of the allowed fields, keep them in the new object.
Object.keys(obj).forEach((el) => {
if (allowedFields.includes(el)) {
newObject[el] = obj[el];
}
});
return newObject;
};
How to use:
const filteredRecord = filterObj(record, 'name'); // arbitrary list of allowed fields. In this case, we'll only allow 'name'.
await Quiz.create(filteredRecord);

How to update/insert an other document in cloud firestore on receiving a create event for a collection using functions

Let us assume that we have two collections say "users" and "usersList"
Upon creating a new user document in users collection with following object
{username: Suren, age:31}
The function should read the above data and update other collection i.e. "usersList" with the username alone like below
{username: Suren}
Let me know the possibility
The code I have tried is
exports.userCreated =
functions.firestore.document('users/{userId}').onCreate((event) => {
const post = event.data.data();
return event.data.ref.set(post, {merge: true});
})
I have done it using below code
exports.userCreated = functions.firestore.document('users/{userId}')
.onCreate((event) => {
const firestore = admin.firestore()
return firestore.collection('usersList').doc('yourDocID').update({
name:'username',
}).then(() => {
// Document updated successfully.
console.log("Doc updated successfully");
});
})
If all you want to do is strip the age property from the document, you can do it like this:
exports.userCreated = functions.firestore.document('users/{userId}').onCreate((event) => {
const post = event.data.data();
delete post.age;
return event.data.ref.set(post);
})

Resources