firebase security-rules write access to only one specific field - firebase-realtime-database

is it possible to give update access to only one specific field ?
users
+ gaglajasdfer32fasfa
- Name: Luke // not allowed to update
- likes: 3 // allowed to update

You will have to specify the rule for each field in that case as shown below:
{
"rules": {
"users": {
"$uid": {
"Name": {
".write": "!data.exists()"
}
}
}
}
}
!data.exists() allows write operation only when it's absent now and hence allowing "Create" operations only and not "update" or "delete".
Checkout the documentation to learn more about security rules.

Related

firebase rules allow only certain user to write key valus

i want to write firebase realtime database rule where certain user can only write cetain key
ex: user with UID underlined in red can write value of key franchise_active only
user with UID underlined in green can write value of key vendor_active only
both users can read
Sounds possible. Something like this should work:
{
"rules": {
"application_status": {
"$uid1": {
"$uid2": {
"franchise_active": {
".write": "auth.uid === $uid1"
},
"vendor_active": {
".write": "auth.uid === $uid2"
}
}
}
}
}
}

Firebase realtime DB rule based on data.child do not work

I like to retrieve the list of items only the ones its delete flag is false. but this setting does not work.
{
"rules": {
"items": {
".read": "data.child('isDelete').val() == false",
"$uid": {
".write": "auth != null && !data.exists()"
}
}
}
}
Here's a result from Rules playground request I tried.
Request details
{
"auth": null,
"resource": {
"key": "value"
},
"path": "/items",
"method": "get",
"time": "2022-07-07T09:24:31.042Z"
}
Result details
Line 4 (/items)
read: "data.child('isDelete').val() == false"
The data structure
items
- 1xxxxxxxxxx
title:"title text 1"
createdAt:"2022-06-05T04:21:57.322Z"
isDelete:false
- 2xxxxxxxxxxxxx
title:"title text 2"
createdAt:"2022-06-05T04:21:50.322Z"
isDelete:true
What is wrong?
I think you may be missing that rules are not filters on their own. All the rules do is check for any incoming operation whether it is allowed, and in your try from the playground you are trying to read all of /items, rather than just the items with isDelete set to false, to that isn't allowed.
There is no way to perform the necessary query from the playground, but in code you can get the items with this query:
ref.orderByChild("isDelete").equalTo(false)
Now the operation matches the condition in your rules, and is only trying to read data that it is allowed to, so the rules will allow the operation.
Update: I forgot that query-based rules in the Realtime Database actually require that you write the rule as this:
{
"rules": {
"items": {
".read": "query.orderByChild === 'isDelete' && query.equalTo === false",
...
Now the query only tries to retrieve non-deleted data, and the security rules correctly validate this.

How can you observe a snapshot of a upper level child w/o overriding the lower child rules?

JSON
"users" : {
"02PdiNpmW3MMyJt3qPuRyTpHLaw2" : {
"Coordinates" : {
"latitude" : -24.809620667034363,
"longitude" : 28.321706241781342
},
"Education" : "6", ........./// here are 10 Childs further on same level as education
"Music"
The current rules
{
"rules": {
".read": false,
".write": false,
, "users": {
"$uid": {
"Education" :{
".read": "$uid == auth.uid",
".write": "$uid == auth.uid"
}
According to my understanding, if I make users read rule for authorized users, it will override the education rule. This below is where users read rule is needed
let artist = Database.database().reference().child("users").queryOrdered(byChild: "music").queryStarting(atValue:dateToday.timeIntervalSince1970*1000)
artist.observe(DataEventType.value, with: { snapshot in
Update: I had a typo "users" at the rules
In general indeed the rule is that if you grant somebody read (or write) access to a node, they have the same access to all data under that node. So you can't just say ".read": true on /people as that will them to read the entire node.
What you can do is specify what query is allowed in your security rules. So to allow every to read all nodes with the given music value, you could do:
"baskets": {
".read": "query.orderByChild == 'music' &&
query.equalTo > 1354867200000"
}
A few things to keep in mind here:
I hard-coded it here, but you will have to calculate the value for the filter in your rules somehow based on the now variable.
You can only filter on a single value, so you can't filter on both music and uid. You may be able to merge the two values into a single property and order/filter on that. For more on this see: Query based on multiple where clauses in Firebase

Listener at /users failed: permission_denied

I have a Sign Up Flow using Firebase. When I check if an email already exists in the database, like so:
refUsers.queryOrdered(byChild: "email").queryEqual(toValue: emailText).observeSingleEvent(of: .value, with: { snapshot in
if (snapshot.value is NSNull) {
print("Unique email")
// Move to Password View.
let passwordViewController = self.storyboard?.instantiateViewController(withIdentifier: "PasswordViewController") as! PasswordViewController
self.navigationController?.present(passwordViewController, animated: true, completion: nil)
// Pass the emailText to the last View of the flow.
self.singleton.sharedInstance.emailText = emailText!
}
else {
print("Duplicate email")
}
})
The problem is, I don't have the permission to view /users in the database cause my rule is:
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
I know I can find if an email is a duplicate using Auth.auth().createUser but it's not just email that I'm checking in the sign up flow. I use the same method for unique username, as well. How can I achieve this?
As you can see this is not the best way to do it. You should not manually check if email already exists - Firebase can do that for you when user signs up and why would you not want to use that?
What you need is a different approach. I can think of two ways right now:
First:
You can add a new rule to Firebase, eg:
{
"rules": {
"usernames": {
".read": true,
".write": "auth != null"
},
"emails": {
".read": true,
".write": "auth != null"
}
}
}
What you do here is create a new node named usernames which every user can access and read.
Here you should hold a copy of all usernames that registered users have and when registering check if users username is already inside this node.
Second way:
You could modify your signup flow a bit and let users register without a username. After account is created you let them set a username. With a nice flow it would all look as the same registration form.
UPDATE
With rules above users should be able to read from emails and usernames without being registered. This way you can fetch data and compare if email or username is already in use.
Just make sure that when user registers you insert his email and username into those two nodes.
Though #ZassX answered helped me, I've learned what a good approach for this would be, for those who are confused like me.
The best approach is to keep users data safe in /users with "auth != null" rule. Only show the user's meta data to everyone that includes just the email and password of each user. For example:
Database
{
“metaData”: {
uid: {
“email”: …,
“password”: …
}
},
“users”: {
uid: {
“email”: …,
“password”: …
// other information
}
}
}
Security
{
"rules": {
“metaData”: {
“.read”: true,
“.write”: “auth !== null”
},
“users”: {
“.read”: “auth !== null”,
“.write”: “auth !== null”
}
}
}
The information in metaData can now be matched without a user being authenticated first. Of course, this can get more complex as you add more security but for easy understanding, this is it.

Controlling data-update on Firebase

I am using Firebase for a small iOS project and I wonder if the following is possible.
Under a given node "myNode_123" I store data records; each record having the shape below:
[fieldStr: "ABC_xyz", fieldNum: 678]
A record is in fact a subnode.
Now my question is about updating the data, for already existing records.
Someone should be allowed to update a data record only if the new value for fieldNum is higher than the one already there.
And in any case the value for fieldStr should stay as it is.
I have already written some rules to make sure fieldNum is fullfilling my request.
But I still have the issue of fieldStr having the risk of being changed.
Here are my rules:
{
"rules": {
".read": true,
//".write": true
"myNode_123": {
"$Record": {
// Ensure that we are either creating a new record not yet existing,
// or that we are only updating for a higher fieldNum.
".write": "(!root.child('myNode_123/'+$Record).exists()) ||
(root.child('myNode_123/'+$Record).exists() && (newData.child('fieldNum').val() > data.child('fieldNum').val()))"
}
}
}
}
The control of fieldNum is working as I wish. But it is still possible to change fieldStr, which I do not want.
Any advice from a Firebase expert?
Add this under $Record.
{
"rules": {
".read": true,
//".write": true
"myNode_123": {
"$Record": {
// Ensure that we are either creating a new record not yet existing,
// or that we are only updating for a higher fieldNum.
".write": "!data.exists() || newData.child('fieldNum').val() > data.child('fieldNum').val()",
"fieldStr": {
".validate": "!data.exists() || data.val() == newData.val()"
}
}
}
}
}
The !data.exists() will make sure only new data can be written to this location. And data.val() == newData.val() will add an exception to allow writes when the new data is the same as the old data, just in case you want to write the entire object to the Database and include the fieldStr.

Resources