my user database contains all users with data like username, rating, description, email.
I want to access all user but, want to set email to be unreadable. Is it possible to set just one property to not be readable?
this is my rule:
"users": {
".indexOn": ["username","id"],
".write": false,
".read": true, //removed
"$user_id":{
"email": {
".read": false,
},
}
},
This is not possible with Firebase Realtime Database. Once a user can read a node, they can read all data inside that node.
You'll want to instead separate the emails into their own top-level node, where you then control access on the entire node.
For more on this see:
How to block reading specific field in child in the firebase
Related
I have a Firebase database that I want to only allow users who have access to that application to be able to read from and write to.
My data structure is like so:
{
"applications": {
"id_1": {
"feature": {
"a": true,
"b": false
},
"users": {
"user_id_1": true,
"user_id_2": true
}
}
}
}
As you can see, each application can have many users who have read/write access.
I only want users in the users object to be able to retrieve that application.
I have rules like so:
{
"rules": {
"applications": {
".read": "auth != null",
".write": "auth != null",
"$appId": {
".write": "data.child('users').child(auth.uid).val() === true",
".read": "data.child('users').child(auth.uid).val() === true"
}
}
}
}
".read": "auth != null", allows any user who is logged in to be able to retrieve all applications. I only want users user_id_1 or user_id_2 to be able to read that application.
In pseudo code, I would do something like this:
{
"rules": {
"applications": {
".read": "only users in `root.applications.$appId.users` can read", // I need to replace `$appId` some how
".write": "auth != null",
"$appId": {
".write": "data.child('users').child(auth.uid).val() === true",
".read": "data.child('users').child(auth.uid).val() === true"
}
}
}
}
How can I restrict it so when user user_id_1 fetches their applications, they only see apps they have access to?
You're hitting a few common problems here, so let's go through them one by one.
1. Security rules can't filter data
You're trying to control in your rule on /applications what application will be returned when a user tries to read that node. Unfortunately that is not possible, because security rules grant all-or-nothing access.
So either the user has access to /applications (and all data under it), or they don't have access to it. You can't set a rule on /applications to grant them access to some child nodes.
In the documentation, this is referred to as rules are not filters and the fact that permission cascades.
2. Avoid nesting data
You're trying to grant access to /applications, but then store two types of data under there for each application. In cases like that, it is usually better to store each type of data as its own top-level list.
So in your case, that'd be:
{
"application_features": {
"id_1": {
"a": true,
"b": false
},
},
"application_users": {
"id_1": {
"user_id_1": true,
"user_id_2": true
}
}
}
This allows you to grant separate access permissions for the application users and its features. While it means you'll have to read from both branches to get all information of each user, the performance difference there is negligible as Firebase pipelines those requests over a single socket
For controlling access and the most scalable data structure, Firebase recommends that you avoid nesting data and flatten your data structure.
3. Model the data in your database to reflect the screens of your app
Since granting anyone access on /applications gives them access to all data under that, you'll likely need another place to store the list of applications for each user.
I usually make this list explicit in my databases, as another top-level list:
{
...
"user_applications": {
"user_id_1": {
"id_1": true
},
"user_id_2": {
"id_1": true
}
}
}
So now when you want to show the list of applications for the current user, you load the IDs from /user_applications/$uid and then look up the additional information for each app with extra calls (which in turn can be pipelined again).
This one is not in the documentation, but a common pattern with NoSQL databases. I recommend checking out my answers to Many to Many relationship in Firebase and Firebase query if child of child contains a value.
Sorry if the title was confusing, I'll do a better job explaining here.
I was reading up on the Firebase security documentation and came across this page: https://firebase.google.com/docs/database/security/quickstart?authuser=0. Specifically I was looking at the "Sample Rules" -> "User" section which contained the following code:
// These rules grant access to a node matching the authenticated
// user's ID from the Firebase auth token
{
"rules": {
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
}
}
}
Assuming I understand this correctly, you will only be able to read YOUR data under the "users" node, and nobody else's.
So in my app I need to retrieve some data about a "friend" (friend count, username, posts they've made, etc) which is stored under my "friend's" user id in the "users" node. So it looks like this:
{
"users": {
"friend_uid": {
"friend_count": 10,
"username": "john",
"posts": [
"message": "post1"
],
"age": 18,
...
}
}
}
How can I do this based on the permissions above? If this is such a common task (getting some basic information about your friend), then why do a lot of tutorials show that you should set the rules to what I pasted above?
How do developers get the data they need? Do they have 2 nodes, "users" and "private_users", where in "users" they store all of the public information for retrieval from anyone, and "private_users" is where they store all the private info about the user and set stricter read/write rules?
Or do they set database rules on the individual attributes like age, name, etc?
You've basically given the answer yourself... You could either separate the data nodes and add an object with public data and one with private data, where both would have different rules for access.
For the public data node of a user, you of course wouldn't set the rules to allow complete public access when reading, but also limit it to users that are in the friend list.
Another option is to set rules on the individual properties, but this makes the rules quite big and harder to maintain, as every additional parameter needs to be added to the rules manually.
An additional thing I see when looking at your data is the missing data denormalisation. Each user object contains a list of messages/posts done by that user. So if you query a user in users/UID, you'd fetch the whole object including the possibly millions of posts/messages. So you should definitely exclude those from the user objects.
I have a client that would like to be able to make a list of restricted emails that can access the data. So anyone else coming to the app can't read/write any data at all ( ideally can't even log in but I don't think that's possible with Firebase? ). Any ideas on how to go about this? I had thought of having an array of accepted emails and checking whether their email existed in the security rules but that didn't seem to work. I had the following in the database:
"validEmails": ["test#test.com"]
and then in the security rules:
".read": "root.child('validEmails').val().indexOf(auth.token.email) > -1"
But it looks like you can't use indexOf in those security rules.
Maybe I need to have a list of acceptable emails, and then when a user signs up it checks whether they're in that list and adds their UID to an accepted list? I guess I could do this through a cloud function or something?
Any help would be much appreciated.
Cheers
Have the list of allowed user's emails in the database:
"whitelist": {
"fred#gmail%2Ecom": true,
"barney#aol%2Ecom": true
}
Since periods are not allowed in keys, you need to escape strings with periods before storing them.
Then in the database rules:
{
"rules": {
"whitelist": {
".read": false,
".write": false
},
".read": "root.child('whitelist').child(auth.token.email.replace('.', '%2E')).exists()",
".write": "root.child('whitelist').child(auth.token.email.replace('.', '%2E')).exists()"
}
}
User's email is accessible via auth.token.email. You need to escape the dots (. -> %2E) and check if the key exists on the whitelist.
These rules don't allow anyone read or write access to the /whitelist section of the database. Modification is only possible via firebase console.
Thanks guys, what I ended up doing was having a list of acceptable emails:
{
"validEmails": ["test#test.com"],
"validUsers": {}
}
and then have a cloud function run to check when a user signed up if their email was in the valid email list. If it was then it added them to the valid users list and if not it deleted the newly created user. I also set up data rules so that only users within validUsers could access the data.
The front-end then handled the redirection etc for invalid users.
Once you enable the authentication module of Firebase I believe you can't restrict it to email addresses or domains. However you could secure your database another way. If your users are already registered and you know their uid, then you can restrict read and write access based on these.
Lets pretend you have an acl object in the database, you can list the users and their uid with their read/write permissions.
These rules will check each request and only allow authorised users to access the data.
{
"acl": {
[
{
"uid: "abc123"
"canRead": true,
"canWrite": true
},
{
"uid": "def456",
"canRead": true,
"canWrite": false
}
},
"secure": {
".read": { root.child('acl').child(auth.uid).child('canRead').val() == true }
".write": { root.child('acl').child(auth.uid).child('canWrite').val() == true }
}
}
I try to get my firebase realtime database rules running correct but have a problem with a single property rule.
My firebase object looks like this example
"products": {
"KUg68BknfYWuEjAKla5": {
"cat": "Pizzas",
"likes": 132,
"item_price": 39.9,
"item_price_single": 39.9,
"name": "Mussarela",
"observation": "",
"product_id": "-KUg68BknfYWuEjAKla5",
"product_image": "massapan-mussarela.jpg",
"quantity": 1
}
My rules for this object look right now like this
"products": {
".read": true,
".write": "root.child('users').child(auth.uid).child('admin').val() == 'user_is_admin'"
".indexOn": ["status", "categoryId"]
},
So basically I allow everybody to read the object but only the admin to write the object. My problem is that the single property "likes" need to be writeable by every authenticated user. Which I would normally do with ".read": "auth != null", but I dont know how to combine them in my rules. Can I set multiple lines with .write? do I need to combine them in one line? I tried all I can think of but without success.
thx in advance
You can specify access to specific child nodes within the rules. For example
products
product_0
likes: 123
item_price: 1.99
product_1
likes: 222
item_price: 4.99
rules that would only allow reading the likes node by all but limit writing to the admin would look something like this (not tested but something along these lines)
{
"rules": {
"products": {
"$each_product": {
"likes": {
".read": true,
".write": "root.child('users').child(auth.uid).child('admin').val() == 'user_is_admin'"
},
"item_price": {
".read": true,
".write": true
}
}
}
}
}
On the other hand the item_price node could be written to and read by all. None of the other child nodes would be accessible by anyone.
Instead of storing the amount of likes, you could make a list of users who have liked that category/item. Something like this:
likes: {
userid_1: true,
userid_2: true
}
That way, you can allow users to edit only their path. Just like you'd normally do with a user list:
".write": "auth.uid === $user"
The true value can be anything really, as users who didn't like the content wont be in the list.
You'll just need to count the number of items in the list to get the number of likes.
And no, you can't have multiple write rules. Instead, use ".validate". Read and write rules cascade, so if one is true, all the others will be ignored. Validate rules don't cascade, they all need to be true
I have a firebase location with all of my app's stored messages as child objects.
I want clients to be able to get each message if they know the id of the message but not download the entire messages table.
What would the security rule for this look like?
Thanks.
You can disallow a read on the parent, but allow reads if the ID is known:
"rules": {
"messages": {
// Disallow enumerating list of messages
".read": false,
".write": false,
"$messageID": {
// If you know the messageID you can read the message.
".read": true,
// Cannot overwrite existing messages (optional).
".write": "!data.exists()"
}
}
}
See https://github.com/firebase/firepano for an example app that uses unguessable URLs for security.