I am coding a microblogging site on Firebase, to which I am very new. I am storing user information (e.g. introduction, profile pictures) and posts the users write like below structure:
{
"posts" : {
"postid" : {
"category": "xx",
"content": "xx",
"uid":"xx"
}
},
"users" : {
"uid" : {
"intro" : "xx",
"nickname" : "xx",
"profile_picture" : "xx"
}
}
}
I'd like make following rules:
Any signed-in users will be able to post, but only will be able to edit their own posts afterwards
For user info, they will only be able to post/edit their own.
I set the database security rules like below.
"users": {
".read": true,
"$uid": {
".write": "auth.uid===$uid"
},
},
"posts": {
".read": true,
".write": "auth!==null",
},
Here are a couple of issues:
a. This rule will enable any bad user to edit any other post that is not his/her own.
b. When trying to write to users node, there is no existing UID child node at the moment and the users are denied permission to write.
How can I change the database rules to solve the two issues? My preference is doing so without rewriting the front-end code or change the database structure...would love to seek some advice here!
To only allow users to write their own posts (or create new posts with their own UID), you can check the value of the uid field in write operations:
"posts": {
".read": true,
".write": "auth !== null && auth.uid === newData.child('uid').val",
},
Related
I am building an ecommerce android app with Firebase how can I isolate different users like Customer and Seller in Firebase Authentication and give access to read and write to their respective database nodes in Firebase Database(Realtime Database)
This can be achieved using a database structure similar to this:
{
"users": {
"someUserId-782nafdca9": {
"name": "Joe Smith",
"type": "customer",
...
},
"someUserId-78sdfgs523": {
"name": "Example Supplier Co.",
"type": "seller",
...
}
},
"dataForSellers": {
...
},
"dataForCustomers": {
...
}
}
with the following rules:
"rules": {
"users": {
"$uid": {
".read": "auth.uid == $uid",
".write": "auth.uid == $uid",
}
},
"dataForCustomers": {
".read": "auth != null && root.child('users').child(auth.uid).child('type').val() == 'customer'",
".write": "auth != null && root.child('users').child(auth.uid).child('type').val() == 'customer'"
},
"dataForSellers": {
".read": "auth != null && root.child('users').child(auth.uid).child('type').val() == 'seller'",
".write": "auth != null && root.child('users').child(auth.uid).child('type').val() == 'seller'"
}
}
Security rules have a number of predefined variables that can be used to check various conditions. The rules above make use of the auth and root variables.
The rules for user data under users/$userId, currently only check to see if the current user matches the user data they are trying to modify (auth.uid == $uid).
The rules for dataForCustomers and dataForSellers first check if the user is logged in (auth != null) and then check that their user data (accessed by root.child('users').child(auth.uid)) contains the correct value for type.
Note 1: As #Doug mentioned in their comment, the documentation for security rules can be found here with further specifics on securing user data.
Note 2: Like the official documentation, this example is not without flaws. For example, at any time with the security rules as above, if a user is logged in, they can:
change themselves between customer and seller if they are logged in to your database.
add garbage to your database
delete any data made by other users (no concept of "author")
Note 3: Don't just nest all your data under "dataForSellers" and "dataForCustomers", these were given as placeholder names for your own data trees such as "products", "shipping", "orders", etc.
Note 4: As you are just beginning with the RTDB, make sure to have a look at the RTDB vs. Firestore article. For ecommerce, you might be better off using Firestore instead just for it's filtering alone.
I have an object called 'Service' This can be read by anyone but changes can only be made by the owner. My firebase rule for this is:-
"Service": {
".read": true,
"$uid": {
".write": data.child('owner').val() == auth.uid"
}
}
Service has a child called 'owner' which is == to the users UI when logging in through firebase Auth.
I also have a User object which can make a Service as a favourite. I'm saving these in the user object as an array of [businessKey:true].
However, I've been asked to also save in the Service object the reverse relation of what users have favourited it. So an array However as a user isn't always the owner of a service I come into a permissions error. I'm trying to write rules that allow anyone to write to one child but not the others.
I have tried...
"Service": {
".read": true,
"$uid": {
".write": data.child('owner').val() == auth.uid" || data.child('users').val() != root.child('users').val()"
}
}
this is would allow a write if the user was the Service owner or just changing the child 'users' The effect was that any user was able to write to anything.
I've also tried
"Service": {
".read": true,
"$uid": {
"$users": {
".write" : true,
},
".write": "data.child('owner').val() == auth.uid"
}
}
thinking this would always allow a write to 'users' and any other child if user was owner.
I'm pretty new to firebase and this rules syntax in general so I'm probably making some glaring error! If what I want to achieve possible? what have I got wrong?
So, I have an app where users can order the cakes and do other profile management, the rules looks like below:
{
"rules": {
"cakes" : {
".read": true,
".write": false
},
"users": {
"$user_id": {
".read": "auth != null && $user_id === auth.uid",
".write": "auth != null && $user_id === auth.uid"
}
}
}
}
Simply, they mean any one can read the cakes node (but no one can write). And an authenticated user can see or write to his on node.
This is good so far.
Now, my requirement is: When someone places an order through the app then i need to store it to firebase db in a top level node (lets say it orders). The question is what kind of security would be placed on orders node?
In functional definition: The app should be able to create new orders as user checks out, no one except seller should be able to read those orders, only seller should be able to have update access to a order.
If you want everybody to be able to write orders, and nobody able to read, the rules are simply the inverse of the ones for cakes:
"rules": {
"orders" : {
".read": false,
"$orderId": {
".write": true
}
},
With this anyone can push new data under /orders. And as long as you use push() to generate the new keys, you're guaranteed that they'll never conflict.
With these rules only a system-level admin can read the data. That is: you can read it in the Firebase Console, or someone can read it if they use the Admin SDK.
You might want to open it up for reading a bit more, e.g. by having the concept of an application-level administrator. Say that your UID is uidOfVik, you could model a list of admins in your database:
admins: {
uidOfVik: true
}
Now you can allow only admins to read the orders with:
"rules": {
"orders" : {
".read": "root.child('admins').child(auth.uid).exists()",
"$orderId": {
".write": true
}
},
So I've set up a simple db/web form to collect some user data. Rn I am trying to figure out the rules thing but I am running into this problem - if my read flag is set to true then I can simply run this in the console
var ref = firebase.database().ref();
ref.on("value", function(snapshot) {
console.log(snapshot.val());
}, function (error) {
console.log("Error: " + error.code);
});
and expose the users which should not be the possibility. If I set read to false then I cant access the DB upfront to validate if email address is unique or not. I guess I need to achieve 2 things:
Prevent db snooping through the dev tools running any snippets
Make sure email address is unique.
p.s. My currents rules (prevent delete, prevent read, make sure POST request has certain fields):
{
"rules": {
"users": {
".read": false,
"$uid": {
".write": "!data.exists()",
".validate": "newData.hasChildren(['first_name', 'last_name', 'email', 'country', 'amount'])"
}
}
}
}
To avoid duplicates you will want a validation check in the DB rather than reading data from the client and checking (you can't trust the client).
Since there is no easy way to check for duplicate child values in a firebase base collection, you will need a separate collection to track emails and then validate your emails against that, i.e.:
{
"rules": {
"users": {
".read": false,
"$uid": {
".write": "!data.exists()",
".validate": "newData.hasChildren(['first_name', 'last_name', 'email', 'country', 'amount'])",
"email": {
".validate": "!root.child('emails').child(newData.val()).exists()"
}
}
},
"emails": {
".read": false,
".write": "!data.exists()"
}
}
}
You will then need to write the users' emails to the email collection as the users are added, e.g.:
var ref = firebase.database().ref('emails/'+email);
ref.set(uid)
I'd like to specify rules in Firebase such that a path can only be accessed if it's known.
/root
/messages
/message1
/message2
/message3
If you access /messages you can receive a permission denied (or nothing).
If you explicitly access /messages/message2 you get the contents.
Update 1: Expected behaviour (iOS)
FIRDatabase
.database()
.reference()
.child("invitations")
.observeEventType(.Value, withBlock: { snapshot in
// snapshot returns nothing or permission denied.
})
FIRDatabase
.database()
.reference()
.child("invitations/message1")
.observeEventType(.Value, withBlock: { snapshot in
// snapshot returns message1
})
This is not an answer, but I believe the OP is asking how to prevent enumeration of the nodes under a given node, while allowing anonymous access if the user knows the DIRECT path of a given node.
I too, have been trying to figure out how to do this. Basically, I'm trying to post semi-sensitive data under a given node, while making the name of the node available securely through a different means. However, I don't want to implement a "security/user" model within Firebase. I'd simply like to use Firebase similar to an Amazon S3 bucket, where if you know the location of a key, you can download the file/data stored at that location, while still preventing enumeration/listing of the nodes within the database.
An example of such usage would be if you generate the keyname using a crypto digest such as SHA2. The key couldn't be easily generated, yet if you knew the key, you could access the node directly. Does anyone know how to do this?
EDIT: I figured out how to do this in Firebase. The solution is rather simple. Here is an example of the JSON security rules in Firebase:
{
"rules": {
"users" : {
".read": false,
".write": false,
"$child" : {
".read" : true,
".write" : true
}
},
"chats" : {
".read" : false,
".write" : false,
"$child" : {
".read" : true,
".write" : true,
},
},
"comments" : {
".read" : false,
".write" : false,
"$child" : {
".read" : true,
".write" : true
},
},
".read": false,
".write": false
}
}
In this example, any node under "users", "chats", and "comments" is directly accessible, but enumeration is no longer possible.
You will need to specify the access inside the message id level.
If you were clear on what you are trying to achieve it will look like this:
{
"rules": {
"messages": {
//if you add the read permission here it will give you access to all messages
"$message": {
//here it will give only to the specific $message.
//if you want to specify the exctly message id you can do something like "$message == 'message1'" on the ".read"
".write": "auth != null",
".read": "auth != null"
}
}
}
}