Techincal Guidences of collection.allow in Meteor JS? [closed] - meteor

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
I need to know the Technical Meaning of collection.allow() in Meteor JS.i had studied meteor document but not understand properly.So Can you please explain the below the terms with use of below code.
what is the doc?
How to check Posts.allow() is return true/false?
How to call the below methods like insert,update & remove when ever clicks a button?
How to write queries to insert , update & remove using the below methods in Meteor JS?
How to check more than one person allows to insert,update & remove queries?
Can you please give me suggestions about above things ?
Posts = new Meteor.Collection("posts");
Posts.allow({
insert: function (userId, doc) {
// the user must be logged in, and the document must be owned by the user
return (userId && doc.owner === userId);
},
update: function (userId, doc, fields, modifier) {
// can only change your own documents
return doc.owner === userId;
},
remove: function (userId, doc) {
// can only remove your own documents
return doc.owner === userId;
},
fetch: ['owner']
});

These methods are used to validate an insert/update/delete that the client requests. If the client calls Posts.insert(somePost). The server will use Posts.allow to validate if this can actually take place. To answer your questions directly:
what is the doc?
The doc in these methods is the document the client passes in. In my above example it would be somePost.
How to check Posts.allow() is return true/false?
Posts.allow() will check to see if a user can insert a post and return true if they can and false if they cannot (this is your responsibility). In your example there must be a valid userId and the document's owner must be the currently logged in user. Since your doc is a JSON object it must have an owners field in this example. If you always return false, then no client will ever be able to create a post. If you always return true, then any request to insert a post will be accepted.
How to call the below methods like insert,update & remove when ever clicks a button?
You actually never call these methods directly. They are called for you when the client attempts to insert/update/delete a Post.
How to write queries to insert, update & remove using the below methods in Meteor JS?
Again, you never actually call these directly, but when you do Posts.insert(somePost), it will automatically attempt to validate against the insert allow method. If it receives a true the post is inserted. If it receives a false it will throw an exception.
How to check more than one person allows to insert,update & remove queries?
Not exactly sure what you mean by this but if you have two people logged in and they both attempt to insert a post you can validate them uniquely given the userId field in the methods.
Update:
I'll elaborate on your comment's question. The document object just has an owner property on it. The document that is passed in may look like something like this (simplified):
doc = {
"name":"My Important Document",
"description": "This is a great document.",
"createdOn": 1394043417621,
"owner": b8QsgX3awg7E9DMKs
}
So doc.owner would give you the document's owner's id. You can then compare it to the userId passed in, to see if they are the same person.

Related

How to set path to a field in Firestore security rule [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
My collection name is "users" and in that i have some documents created in random number and when i click that i get my fields as
name : john
swimming : true
uid : (auth.uid)
Since my document is created in random numbers i can't access that document directly as request.auth.uid
Now i want to know how can i write security rules to stop the user from updating his swimming field as false?
EDIT
What i have tried so far,
match/users/{document=**}
{
allow read;
}
match/users/{document=**}/swimming
{
allow write : if false;
}
And
match/users/{document=**}
{
allow read;
}
match/users/{document=**}/{swimming}
{
allow write : if false;
}
And
match/users/{document=**}
{
allow read;
}
match/users/{document}/swimming
{
allow write : if false;
}
For my 'users' collection, I create documents with the ID set to the UID of the user in question using the set() function instead of the add() function. An alternative would be to do as you have: use the add() and explicitly set a uid field.
There are many blog posts that answer what you are after. Googling for "firestore rules restrict updates of some fields" yields lots of results including:
https://medium.com/day4/firebases-firestore-rules-how-the-f-af5821513025
https://kushagra.dev/blog/ensure-missing-fields-in-firestore-security-rules/
Firestore rules for document field
Ultimately, in your rule you can compare the request.resource.data.XXX (field XXX in the uploaded data) with resource.data.XXX (XXX in the existing data in Firestore), and return FALSE if you want to block the user from updating field XXX. You can also use boolean operators to enforce a no-edit rule on multiple field.

Firestore per-field security rule

I have studied the answer to this question (which has an extremely similar title): Per field rules in Firestore Security Rules. The solution in that case was to make a field unmodifiable, I do not think that is what I am after here.
I have a posts collection with a data structure as follows.
{
uid: string, // this is the original poster's UID
title: string,
content: string,
likesCount: number,
likerUIDs: string[]
}
I would like to restrict writes to the title and content fields to users with an auth token UID that matches the post's uid field. However, any authenticated user should be able to increment the likesCount and add their own UID to the likerUIDs field.
It seems like per-field security rules are not really supported. Is the solution here to maintain a separate collection with different rules but the same keys as the posts, for example post-likes, that contains the likesCount and likerUIDs fields? Or is there a firestore security rule trick to achieving this?
EDIT
Thanks to Doug and Frank's comments (extremely helpful video series by the way), I was able to come up with a solution to my initial question. As suggested in the accepted answer, I'm going to do this with a callable function, since it is perfect for this case. For those who stumble upon this question and want to accomplish something similar, I've pasted the rules I ended up with here. These rules accomplish exactly what is described in the question, but a callable function was definitely the way to go here.
function isOwnerCurrent() {
return request.auth.uid == resource.data.uid;
}
function isOwnerIncoming() {
return request.auth.uid == request.resource.data.uid;
}
function isUnmodified(key) {
return request.resource.data[key] == resource.data[key]
}
match /posts/{post} {
function validateNonOwnerPostUpdate() {
return isUnmodified('title') && isUnmodified('content') &&
isUnmodified('created') && isUnmodified('updated');
}
allow read: if true;
allow create: if isOwnerIncoming();
allow update: if (isOwnerCurrent() || validateNonOwnerPostUpdate()) && isUnmodified('uid');
allow delete: if isOwnerCurrent();
}
For updates, I am checking if the user is either the owner of the post, or only updating the so-called "public" fields of likesCount and likerUIDs, and for both they must not be modifying the owner UID of the post. Like mentioned in the accepted answer, this isn't great because anyone will be able to edit these fields and mess up the numbers.
I think it is better to use cloud function to solve this. you can use callable cloud function when that other users (not document owner) like that post. https://firebase.google.com/docs/functions/callable . because cloud function can bypass security rules
I think it is safer you do it through cloud function unless that likesCount is not that important. because if someone can hack your client app than they can modify your code. you will update the document like this
db.doc(`posts/${postID}`).update({
likesCount: admin.firestore.FieldValue.increment(1)
})
if they hack your app, then they can change the increment from 1 to 100 for example. yes you can do the same via security rules but you have to add additional check , and it will be complicated and error prone IMO.

delete item out of firebase database

I have a restaurant bookmarks list in my firebase, but I don't know how to delete a specific restaurant in my database.
So I have the function unfavorite(favorite) where I pass the favorite restaurant as a parameter. Till here, than I want to pass this parameters id to the query to remove from the database like this:
this.afDb.list(`bookmarks/${user.uid}/restaurant/${favorite.restaurant.id})`).remove();
here is a screenshot of my database list:
How can I remove that specific restaurant out of the bookmarks list?
You will first need to add an ".indexOn": ["id"] rule to your database something like this:
"bookmarks": {
"$user_id": {
// normal reads and write rules here
},
".indexOn": ["id"]
This step is necessary for firebase database because otherwise you wont be able to use the orderByChild() and equalTo() methods.
Then, where you have your delete function, you want to instead use:
exampleRef = yourDb.ref("bookmarks/${user.uid}"); //this is just to simplify your reference a bit
exampleRef.orderByChild('id').equalTo(theDeleteIDhere).once('value').then(snapshot => {
snapshot.forEach((restaurant) => {
restaurant.ref.remove();
});
}); //this is a Promise that you can modify to return "true" if successful for example
The example I provided is just the way I have done it before (i.e. I prefer to use promises; hence the then() becuase this makes it easier to return that promise in an angular service which allows me to check whether the request was successful). You can use any variation of this so long as you have the "indexOn" rule and you use any sort of "sorting" method firebase provides here
Method 2
When I wrote this I totally glanced over the ability to map your restaurants like such:
Lets say your project is already listing those restaurants. You can therefore save each restaurant's auto generated id to a variable or map:
restaurants; // this is a map like this <your-identifier:autoID>
you can then easily just call:
exampleRef.child(restaurants[yourIdentifier]).remove();

Firestore security rules based on map values

I want to store if a user is permitted to read a document in the document itself, based on the user's email address. Multiple users should have access to the same document.
According to the documentation Firestore does not allow querying array members. That'S why I'm storing the users email addresses in a String-Bool Map with the email address as a key.
For the following example I'm not using emails as map keys, because it already doesn't work with basic strings.
The database structure looks like that:
lists
list_1
id: String
name: String
owner: E-Mail
type: String
shared:
test: true
All security rules are listed here:
service cloud.firestore {
match /databases/{database}/documents {
match /lists/{listId=**} {
allow read: if resource.data.shared.test == true
}
}
}
Edit: It also doesn't work if I use match /lists/{listId} instead of match /lists/{listId=**}
How I understand it, this security rules should allow reading access to everyone if the value in the map shared[test] is true.
For completness sake: This is the query I'm using (Kotlin on Android):
collection.whereEqualTo("shared.test", true).get()
.addOnCompleteListener(activity, { task ->
if (task.isSuccessful) {
Log.i("FIRESTORE", "Query was successful")
} else {
Log.e("FIRESTORE", "Failed to query existing from Firestore. Error ${task.exception}")
}
})
I'm guessing that I cannot access map values from the security rules. So what would be an alternative solution to my problem?
In the Firestore rules reference it's written that maps can be accessed like that resource.data.property == 'property' so, what am I doing wrong?
Edit: This issue should be fixed now. If you're still seeing it (and are sure it's a bug with the rules evaluator), let me know in the comments.
I've chatted with some folks here about the problem you're encountering, and it appears to be an issue with the security rules itself. Essentially, the problem seems to be specific to evaluating nested fields in queries, like what you're doing.
So, basically, what you're doing should work fine, and you'll need to wait for an update from the Firestore team to make this query work. I'll try to remember to update this answer when that happens. Sorry 'bout that!
Whenever you have (optional) nested properties you should make sure the property exists before continuing to check its' value eg.
allow read: if role in request.auth.token && request.auth.token[role] == true
in your case:
allow read: if test in resource.data.shared && resource.data.shared.test == true
, I was struggling a long time with roles until I realized that on non-admin users the admin field is undefined and firestore rules just crashes and doesn't continue checking other possible matches.
For a user without token.admin, this will always crash no matter if you have other matches that are true eg:
function userHasRole(role) {
return isSignedIn() && request.auth.token[role] == true
}

How to prevent collection modification via console for an otherwise secure update operation?

I am fairly new to Meteor and am just trying to figure out meteor security.
I am writing a quiz app that allows a logged in user to save their scores. I have created a collection which consists of a user id and an array of scores. The way I expose a push of new score is a method on the server side:
Meteor.methods({
'pushScore' : function(playerId, playerScore) {
UserScores.upsert({ userId : playerId}, {$push : {scores : playerScore}});
}
});
I call the method on click of a button from the client like so:
if (Meteor.userId()){
Meteor.call('pushScore', Meteor.userId(), Session.get("score"));
}
I have the following concerns here:
Obviously the user can manipulate the score value in "Session" and cheat the system. What could be an alternate secure mechanism to keep track of the running score while a quiz is being taken?
The other one is probably a bigger concern. How do I prevent the user from just firing a console call to my method "pushScore" and again cheat the system by adding, say a score of 100?
Is there an inherent flaw in the way I have designed here?
This is just a sample application, but I can easily imagine a real world scenario which could mimic this. What woudl be a best practice in such a scenario?
Thanks in advance.
Cheers..
As #Peppe suggested, you should move the logic to the server somehow. The main rule for Meteor security (and web security in general) is
You cannot trust the client.
The reason for that is what you've already mentioned: if there is something a client can do, then there is no way to stop a rogue user to do the same thing from the browser console, or even to write his own malicious client that will exploit the leak.
In your case, that means that if client is able to add points to scores, then the user is able to do so as well, regardless on what security measures you employ. You can make this more or less difficult, but your system has a designed leak which cannot be completely closed.
Thus, the only bulletproof solution is to make the server decide on when to assign points. I assume that in a quiz app user gets points when he choose the right answer to a question. So instead of checking that on the client, create a server–side method that will receive the question ID, answer ID, and increase user scores if the answer is correct. Then make sure user cannot just call this method with all possible answer, with a way that corresponds to your quiz design – for example give negative points if wrong answer is chosen, or allow to answer the same question only once in a period of time.
Finally, make sure the client doesn't just get the correct answer ID in the data it receives.
In a nutshell, there are 2 common soloutions to your problem:
if you're using a Meteor.method dont pass any arguments in the Meteor.call, the server can and should gather the data it plans to insert/update on the server side.
you can add a validation function to the collection using the collection "allow" method to verify any updates from the client, in that case you don't need the Meteor.method and can just update from the client and validate it server-side.
Security (insert/update/delete operations) in meteor works in the same way as security in any other framework: before executing an action taken by the user, make sure the user has the rights to perform it. Security may appear as a weakness in Meteor, but it does not suffer from it any more than other frameworks (though, it's easier to exploit it in Meteor through the console).
The best way to solve it probably varies from case to case, but here's an example: if a user posts a post, the user should gain 5 points. Here's a bad way to solve it:
if(Meteor.isClient){
// Insert the post and increase points.
Posts.insert({userId: Meteor.userId(), post: "The post."})
Meteor.users.update(Meteor.userId(), {$inc: {'profile.points': 5}})
}
if(Meteor.isServer){
Posts.allow({
insert: function(userId, doc){
check(doc, {
_id: String,
userId: String,
post: String
})
// You must be yourself.
if(doc.userId != userId){
return false
}
return true
}
})
Meteor.users.allow({
update: function(userId, doc, fieldNames, modifier){
check(modifier, {
$inc: {
'profile.points': Number
}
})
if(modifier.$inc['profile.points'] != 5){
return false
}
return true
}
})
}
What makes it bad? The user can increase his points without posting a post. Here's a better solution:
if(Meteor.isClient){
// Insert the post and increase points.
Method.call('postAndIncrease', {userId: Meteor.userId(), post: "The post."})
}
if(Meteor.isServer){
Meteor.methods({
postAndIncrease: function(post){
check(post, {
userId: String,
post: String
})
// You must be yourself.
if(post.userId != this.userId){
return false
}
Posts.insert(post)
Meteor.users.update(this.userId, {$inc: {'profile.points': 5}})
}
})
}
Better, but still bad. Why? Because of the latency (the post is created on the server, not the client). Here's a better solution:
if(Meteor.isClient){
// Insert the post and increase points.
Posts.insert({userId: Meteor.userId(), post: "The post."})
}
if(Meteor.isServer){
Posts.allow({
insert: function(userId, doc){
check(doc, {
_id: String,
userId: String,
post: String
})
// You must be yourself.
if(doc.userId != userId){
return false
}
return true
}
})
Posts.find().observe({
added: function(post){
// When new posts are added, the user gain the points.
Meteor.users.update(post.userId, {$inc: {'profile.points': 5}})
}
})
}
The only disadvantage this solution suffers from is the latency of the increment of the points, but it is something we must live with (at least at the moment). Using observe on the server may also be a disadvantage, but I think you can get pass it by using the package collection hooks instead.

Resources