Firebase rules and validations for update existing data - firebase

I have a data structure as following:
{
"users" : {
"31J59dq1clZ3inHMzoopiigsEg63" : [ {
"name" : "Lanchana",
"timestamp" : 1516916245423,
"uid" : "31J59dq1clZ3inHMzoopiigsEg63",
"userEmail" : "*****#gmail.com",
"total-stars" : 123
} ]
}
}
and fire rules as following:
{
"rules": {
"users": {
"$uid": {
".read": "$uid === auth.uid && auth != null",
".write": "!data.exists() || data.child('uid').val() ==
auth.uid && auth != null",
}
}
}
}
I can add new data successfully but I cannot make any updates to the existing one. for ex: If I want to update only total-stars of a particular user based on the uid, how can I specify write and validation rules?

Your data structure seems to have a layer 0 that is missing from the rules. If that layer indeed exists in your actual JSON, you'll want to have it in your rules too:
{
"rules": {
"users": {
"$uid": {
"$postid": {
".read": "$uid === auth.uid && auth != null",
".write": "!data.exists() || data.child('uid').val() ==
auth.uid && auth != null",
}
}
}
}
I called the layer $postid here. You may want to use a name that better reflects what the layer represents.

Related

hi, my realtime database rules are not making sence, i want the int to only be allowed to go up by 1, not down, nor up more than 1

here is my rules code, the code updates in unity every time the user gets +1 point. the rules need
to make it that points can only be updated by +1 (basically , currentPoints = newPoints +1).
in unity i would write it as:
if (currentPoints < newPoints) currentPoints++;
now how do i state my rules to stick to that so my code cant be hacked/edited by a hacker to give
them +1000 points at a time instead of +1 on the database.
{
"rules": {
"users": {
".read": "auth != null",
"$userId": {
".write": "$userId === auth.uid",
"points": {
".validate": "newData.isNumber()",
".write": "data.val() == null && newData.val() == 1 || newData.val() === data.val() + 1"},
"username": {
".validate": "newData.isString()"}
}
}
}
}
Looking at your current rules, you get caught out by cascading security rules:
{
"rules": {
"users": {
// any logged in user can read another user's data
".read": "auth != null",
"$userId": {
// a user can modify any of their own data
".write": "$userId === auth.uid",
"points": {
// asserts points is a number
".validate": "newData.isNumber()",
// this next rule is ignored when $userId === auth.uid - read/write rules cascade!
// if $userId !== auth.uid, ANYONE can write to this location as long as points is first set to 1 or is increased by 1
".write": "data.val() == null && newData.val() == 1 || newData.val() === data.val() + 1"
},
"username": {
// asserts username is a string
".validate": "newData.isString()"
}
}
}
}
}
With your current rules, as long as I am logged in, I can get the value of /users/someUserId/points, increase it by 1, and then set the new value to /users/someUserId/points. Arguably, the only person who should be able to increase a user's points is the user who owns that score.
To fix this, you need to remove the ".write" rule (which causes this bug) and move it's logic into ".validate":
{
"rules": {
"users": {
// any logged in user can read another user's data
".read": "auth != null",
"$userId": {
// a user can modify any of their own data
".write": "$userId === auth.uid",
"points": {
// asserts points is a number and is correctly increased by 1
".validate": "newData.isNumber() && ((data.val() == null && newData.val() == 1) || newData.val() === data.val() + 1)",
},
"username": {
// asserts username is a string
".validate": "newData.isString()"
}
}
}
}
}
Note: With the ".read": "auth != null" rule for /users, make sure that any private user data, e.g. emails, phone numbers, etc are not under /users - they should be moved into a different tree called /privateUserData (or similar).

Deny creation of child node, if the node key is not the same as user id. Firebase Rules

Suppose I have a database structure as follows:
/
favorites
ownerid_1
favorite1_id
title
link
favorite2_id
title
link
ownerid_2
etc
I can easily put Firebase permissions around these as follows:
{
"rules": {
"favorites": {
"$owner_id": {
".read": "root.child('favorites').child($owner_id) == auth.uid",
".write": "root.child('favorites').child($owner_id) == auth.uid",
}
}
}
}
However favorites are optional and the user can decide when to create their first favorite. The user's "$user_id" data structure does not exist until they do.
I'm struggling to work out how I can put in a validation rule at /favorites that will allow a new child node to be created by the user only when the key is the same as their user id.
i.e. ownerid_1 cannot create an ownerid_2 node.
I've tried the following but the simulator fails without specifying a specific line:
{
"rules": {
"favorites": {
".validate": "newData.hasChild(auth.uid)",
"$owner_id": {
".read": "root.child('favorites').child($owner_id) == auth.uid",
".write": "root.child('favorites').child($owner_id) == auth.uid",
}
}
}
}
Update #1
Here's the logs without specifying any line in the Rules:
Type write
Location /favorites/
Data { "JhDa8owfAkTRR5qMbuvAgEyUHYL2": { "favid1": { "title": "google", "link": "www.google.com" } } }
Auth { "provider": "google", "uid": "JhDa8owfAkTRR5qMbuvAgEyUHYL2" }
Admin false
Update #2
I have tried the following:
{
"rules": {
"favorites": {
".write": "newData.hasChild(auth.uid)",
// This too: ".write": "newData.hasChildren([auth.uid])",
"$owner_id": {
".read": "root.child('favorites').child($owner_id) == auth.uid",
".write": "root.child('favorites').child($owner_id) == auth.uid",
}
}
}
}
Which works, except it also allows the following to be successfully written:
"favorites": {
"bad_data": "oops",
"$owner_id": {
"favoriteid_1": {
"title": "google.com",
"link": "www.google.com"
}
}
}
It feels like we need an option like "onlyChildren" so that use cases of arbitrary data being written can be prevented.
Any thoughts?
The problem might be in you trying to get the child of a node that does not exist when you do:
"root.child('favorites').child($owner_id) == auth.uid"
Change your rules to:
{
"rules": {
"favorites": {
".validate": "newData.hasChild(auth.uid)",
"$owner_id": {
".read": "$owner_id === auth.uid",
".write": "$owner_id === auth.uid"
}
}
}
}
I changed my approach and it seems to have done the job. This basically says, you can write here as long as you're authenticated and you're not writing null... but if you do, then within the $owner_id tree...
The $owner_id must match your auth.uid, ensuring you only write to your location.
Both a 'title' and 'link' child is required, ensuring you do not write arbitrary data within /favorites.
The data type must be correct, ensuring good house keeping.
Here's the validation rules:
{
"rules": {
"favorites": {
".write": "auth.uid != null && newData.val() != null",
"$owner_id": {
".read": "$owner_id == auth.uid",
".validate": "$owner_id == auth.uid &&
newData.hasChildren(['title', 'link']) &&
newData.child('title').isString() &&
newData.child('link').isString()"
}
}
}
}
Update
Added check for null value.

Firebase Security & Rules, How can I let users delete their own data?

My data in firebase looks like this, in my web app everyone who accesses it gets authenticated anonymously via firebase, and their UID is stored with every post the user creates:
"-KF5N2V_dKD1dMHebUqc" : {
"note" : "Hello everybody",
"pos" : {
"lat" : 40.3628851,
"lng" : -74.0493175
},
"time" : 1460395853884,
"uid" : "f8cf7863-5607-4e2b-97d7-6a121261466c"
},
"-KHwyP-tnWNOA3nxzEm4" : {
"note" : "hi",
"pos" : {
"lat" : 37.0947156,
"lng" : -121.0179501
},
"time" : 1463459362615,
"uid" : "f8cf7863-5607-4e2b-97d7-6a121261466c"
}
I want my firebase rules setup so that only anonymous users can delete their through own posts.
So far i was only able to come up with this after reading the firebase documentation:
{
"rules": {
".read": "auth != null",
".write": "auth != null",
"$msg": {
".validate": "newData.hasChildren(['note','time','uid','pos'])
&& newData.child('note').isString() && newData.child('time').isNumber()
&& newData.child('uid').isString() && newData.child('note').isString()
&& newData.child('pos/lat').isNumber() && newData.child('pos/lng').isNumber()"
}
}
}
You'll need to move the .write permission down and tie it to the data:
{
"rules": {
".read": "auth != null",
"$msg": {
".write": "!data.exists() || (!newData.exists() && data.child('uid').val() === auth.uid)"
".validate": "..."
}
}
}
It's a bit of mix-and-match from these two sections of the Firebase documentation:
https://www.firebase.com/docs/security/guide/securing-data.html#section-data-variables
https://www.firebase.com/docs/security/guide/user-security.html

Firebase data structure and security rules

Im having trouble with the security rules for firebase and Im not 100% where I am going wrong. I am thinking that maybe I have my data structure wrong:
{
"users": {
"uid": {
"displayName": "Name";
}
},
"modules": {
"id": {
"title": "buttons",
"uid": "(user id string)"
},
"id": {
"title": "navbars",
"uid": "(user id string)"
}
},
"snippets": {
"id = moduleID": {
"id (of snippet)": "(id string)" {
"uid (user ID)": "(string)",
"body": {
"css": "(some code)",
"html": "(Some code)",
"name": "(string)",
"description": "(string)"
}
}
}
}
Everything in the app works fine, but when I started to add security rules I got access denied errors. Im just wondering if I have the data structure correct in the first place or is the security rules completely wrong?
Security rules:
{
"rules": {
"users": {
"$uid": {
// grants write and read access to the owner of this user account whose uid must exactly match the key ($uid)
".write": "auth != null && auth.uid == $uid",
".read": "auth != null && auth.uid == $uid"
}
},
"snippets": {
"$uid": {
// grants write and read access to the owner of this user account whose uid must exactly match the key ($uid)
".write": "auth != null && auth.uid == $uid",
".read": "auth != null && auth.uid == $uid"
}
},
"modules": {
"$uid": {
// grants write and read access to the owner of this user account whose uid must exactly match the key ($uid)
".write": "auth != null && auth.uid == $uid",
".read": "auth != null && auth.uid == $uid"
}
}
}
Any advice would be greatly appreciated.
It seems the rules are malformed based on the data structure.
The rules have $uid's in each node but your data doesn't match that. Users has uid but modules has id and snippets has id = moduleID.
$uid is a variable that holds the node name so it can be referenced inside { } so you should (for readability) rename that variable in the other two nodes to something that makes more sense inside each {}. Like in modules, have it $module_id.
However. I think the jest of this is you want to limit reading snippets and modules to authenticated users. To do that, you can reference the users node.
a .read rule would be something like this
"modules": {
"$module_id": {
".read": "auth != null && root.child('users/' + auth.id).exists()
}
So your modules node can be read by a user that is auth'd and their uid also appears in the users/ node
Are you using the Firebase Bolt compiler for rules? I had to write some complex rules and doing it by hand gets confusing very quickly.
Below is what it would looks like. Very easy to make changes, compile and try them out.
//current logged in user
isUser(uid) = auth != null && auth.uid == uid;
//does this module id exist
hasValidModule(module_id) = root['modules'][module_id] != null;
//dont let anyone read or write to top node
path / {
read() = false;
write() = false;
}
path /users/$user_id
{
write() = isUser($user_id);
read() = isUser($user_id);
}
path /snippets/$module_id/$snipit_id/$user_id
{
write() = isUser($user_id) && hasValidModule($module_id);
read() = isUser($user_id);
}
path /modules/$user_id
{
write() = isUser($user_id);
read() = isUser($user_id);
}
Here's the json it spits out:
{
"rules": {
"users": {
"$user_id": {
".read": "auth != null && auth.uid == $user_id",
".write": "auth != null && auth.uid == $user_id"
}
},
"snippets": {
"$module_id": {
"$snipit_id": {
"$user_id": {
".read": "auth != null && auth.uid == $user_id",
".write": "auth != null && auth.uid == $user_id && newData.parent().parent().parent().parent().child('modules').child($module_id).val() != null"
}
}
}
},
"modules": {
"$user_id": {
".read": "auth != null && auth.uid == $user_id",
".write": "auth != null && auth.uid == $user_id"
}
}
}
}
There's some info on the Firebase blog but the doc that really helped me is this
https://github.com/firebase/bolt/blob/master/docs/language.md

Firebase security rules, setting a children not readable?

So I have this db structure:
Under profile I want email & provider-name to be readable only for admin and
Username readable for every logged in user.
How I can achieve that?
Here is my rules:
{
"rules":
{
"users":
{
"$uid":
{
// grants write access to the owner of this user account whose uid must exactly match the key ($uid)
".write": "auth !== null && auth.uid === $uid",
"profile":
{
// grants read access only for registered users
".read": "auth !== null",
"email":
{
// This doesn't work with firebase as I was reading doc.
".read": false
}
}
}
}
}
}
So after a bit of research and reading about denormalize structure I guess this way will work. The fact is that I'm tempted to nest, but probably is a bad idea on firebase.
{
"rules":
{
"users":
{
"$uid":
{
// grants write access to the owner of this user account whose uid must exactly match the key ($uid)
".write": "auth !== null && auth.uid == $uid",
"public-profile":
{
// grants read access only for registered users
".read": "auth !== null"
}
}
},
"private-profile":
{
"$uid":
{
".read": "root.child('users').child(auth.uid).child('role').child('admin').val() === 'true' && root.child('users').child('1').child('role').child('admin').val() === 'true'",
".write": "root.child('users').child(auth.uid).child('role').child('admin').val() === 'true' && root.child('users').child('1').child('role').child('admin').val() === 'true'"
}
}
}
}

Resources