Firebase rules for comment on a post - firebase

What should be the firebase rules for comment on post which is similar to facebook.
There are two things:
first, only authenticated user can comment.
Second, only the user who has commented can delete the comment. The user who has commented his id is saved in username.

I strongly suggest using Firebase Bolt for writing/compiling Firebase Database Security rules. Data structure can get big and complicated. Using Bolt language you'll be able to easily write complex access and structure rules that can be re-used for other db patterns.
Your rules would look something like this:
path /comment/{postUid}/{commentUid} is Comment {
read() { true }
write() { isAuthor(this) || isAuthor(prior(this)) }
}
type Comment {
text : String,
username : String
}
isAuthor(value) { auth != null && value.username == auth.uid }
Pay attention to isAuthor(prior(this)) call. This is the way to make sure only author can delete a comment. prior function returns data as it was saved before current event (create, update or delete).
After using firebase-bolt tool to compile rules to JSON format you'll get:
{
"rules": {
"comment": {
"$postUid": {
"$commentUid": {
".validate": "newData.hasChildren(['text', 'username'])",
"text": {
".validate": "newData.isString()"
},
"username": {
".validate": "newData.isString()"
},
"$other": {
".validate": "false"
},
".read": "true",
".write": "auth != null && newData.child('username').val() == auth.uid || auth != null && data.child('username').val() == auth.uid"
}
}
}
}
}

Related

Firebase hierarchy security rules

I have a problem restricting access to the children of the object
The rules I need:
roles - read
-- UID
--- SUPUSR
---- settings = read only
--- store = write and read
My rules
"roles":{
".read":"auth != null",
".write":"root.child('roles/SUPUSR/').child(auth.uid).child('settings').child('pri_enabled').val() == 1 || root.child('roles/USERS/').child(auth.uid).child('settings').child('pri_enabled').val() == 1",
"settings":{
".read":"auth != null",
".write":false
}
If I leave it the way it is above, it inherits the "roles" rules for writing
Firebase Realtime Database Rules cascade, once you grant permission, you cannot revoke it. So if you allow write access on /roles, anyone can write to any child of /roles whether it's their own or someone else's data.
Other notes:
The current rules affect /roles and /roles/settings, which is too high in the database tree, you should be setting the rules of /roles/SUPUSR/someUserId, /roles/SUPUSR/someUserId/settings and so on.
The use of auth != null seems out of place. Should any logged in user be able to read any other user's roles? Should this only work for super users?
Some of the data would also make sense to be validated.
{
"rules": {
"roles": {
"SUPUSR": {
"$uid": {
// any data under /roles/SUPUSR/$uid is readable to logged in users
".read": "auth != null",
"nome": {
// only this user can update nome, it also must be a string
".write": "auth.uid === $uid",
".validate": "newData.isString()"
},
"role": {
// only this user can update role, and it must be one of a select number of string values
".write": "auth.uid === $uid",
".validate": "newData.isString() && newData.val().matches(/^(R&S|Admin|etc)$/)"
},
"store": {
".write": "root.child('roles/SUPUSR/').child(auth.uid).child('settings').child('pri_enabled').val() == 1 || root.child('roles/USERS/').child(auth.uid).child('settings').child('pri_enabled').val() == 1"
}
// any other keys are ".write": false, by default, which includes "settings"
}
}, // end /rules/roles/SUPUSR
"USERS": {
"$uid": {
...
}
}, // end /rules/roles/USERS
...
}, // end /rules/roles
...
}
}

Firebase rules verify two values from the same data (unique) [duplicate]

I'm creating an application which lets users create items and then allow other users to subscribe to those items. I'm struggling to craft a rule that will prevent users from subscribing more than once to an item.
Here is an example of my data structure (anonymized, hence the "OMITTED" values):
{
"OMITTED" : {
"name" : "Second",
"body" : "this is another",
"userName" : "Some User",
"userId" : "OMITTED",
"created" : 1385602708464,
"subscribers" : {
"OMITTED" : {
"userName" : "Some User",
"userId" : "OMITTED"
}
}
}
}
Here are my Firebase rules at present:
{
"rules": {
".read": true,
".write": "auth != null",
"items": {
"$item": {
".write": "!data.exists()",
".validate": "newData.hasChildren(['name', 'body', 'userId', 'userName']) && newData.child('userId').val() == auth.id",
"subscribers": {
"$sub": {
".validate": "newData.hasChildren(['userId', 'userName']) && newData.child('userId').val() != data.child('userId').val()"
}
}
}
}
}
}
How can I prevent users from subscribing more than once? What is the rule I need to prevent duplicate users within the subscribers list based on userId?
Since security rules can't iterate a list of records to find the one containing a certain bit of data, the trick here is to store the records by an ID which allows for easy access. There is a great article on denormalization which offers some good insights into this practice.
In this case, if your use case allows, you may simply want to switch your data structure so that records are stored by the user's id, rather than storing the ID as a value in the record, like so:
/users/user_id/items/item_id/subscribers/user_id/
In fact, as you'll see in denormalization, you may even benefit from splitting things out even farther, depending on the exact size of your data and how you'll be reading it later:
/users/user_id
/items/user_id/item_id
/subscribers/item_id/user_id
In either of these formats, you can now prevent duplicates and lock down security rather nicely with something like this:
{
"users": {
"$user_id": { ".write": "auth.id === $user_id" }
},
"subscribers": {
"$subscriber_id": { ".write": "auth.id === $subscriber_id" }
}
}

firebase with auth and without auth security rules

I want to create a firebase rule where people can use the database without having to login. But I also want to make a private user node where only the user can acces it by authenticating so far I have something like this. But this trows an error
Error saving rules - Line 6: Expected '}'.
{
"rules": {
".read": true,
".write": "newData.exists()"
},
"test": {
"$uid": {
".read": "auth != null && auth.uid == $uid",
".write": "auth != null && auth.uid == $uid"
}
}
}
I do not understand why the above is not possible
But if I do only:
{
"rules": {
".read": true,
".write": "newData.exists()"
}
}
This wil work so that anyone can use the current data but I want to have something private like "Test" where the people who authenticated themself only have access to
So to be clear I want everyone to use the current database but I also want to have some private parts like test only accesable for registered users
Have a look at https://firebase.google.com/docs/database/security/securing-data
You cannot add an element after "rules". It should be like:
{
"rules": {
...
}
}
and not like
{
"rules": {
....
},
....
}

How to structure data with Firebase to allow data to be user specific and also public?

We are building a platform using Firebase Realtime Database and I'm having a bit of a struggle to find the best way to structure our data for private and public access.
Today we have
database: {
items: {
$userUid: {
$itemUid: {
poster_link: "..."
format: "..."
title: "..."
}
}
}
}
All our items are stored under each user in order to make it fast and secure to load.
Our rules are set up like this
{
"rules": {
"items": {
"$userId": {
"$itemId": {
".read": "auth !== null,
".write": "auth !== null"
}
}
}
}
}
So only an authorised user can read and write the data. I could create something like this to allow items to be public if the value is true:
".read": "auth !== null || data.child('public').val() == true"
But this will still be under $userUid
So I was wondering if you have any suggestion on how to structure this example to allow items to be under a user and also seen publicly, not necessary under this user, a bit like Dropbox does when you share something.
You chosen data structure does not take advantage of the flat data principles of Firebase. This will make it very difficult for you to query items of multiple users. For example, how do you get all the public items without drilling into each user?
Similarly, a boolean called public is also not good because you can't extend it to other ACL scenarios. Much better is an ACL object that can be extended in the future.
For example:
items: {
itemsUid: {
[...],
userId: ...,
ACL: { public: true }
}
}
Now you can write the rule:
auth !== null && (root.child(items/ACL/public).exsists() || data.userId === auth.UID)
If in three months you add a concept of friends that can see you posts or followers that can see you items you can simply add friends: true, followers: true to the ACL object and adjust the rule.
You can structure like this
database: {
items: {
$itemUid: {
poster_link: "..."
format: "..."
title: "..."
user: "userid"
}
}
}
now set the rules as
{
"rules": {
"items": {
"$itemId": {
".read": "auth !== null || data.child('public').val() == true,
".write": "auth !== null"
}
}
}
}

Firebase Database Rules for Unique Usernames

I'm trying to create a website that uses Firebase's databases to store user information. I want to use unique usernames. I have two indexes, one for users and another for usernames.
My database is structured like this:
users {
$uid {
username: "username1",
gender: "xyz"
email: "xyz"
}
},
usernames {
"username1": $uid"
}
The users claim a username with their $uid.
These are my rules:
{
"rules": {
"users": {
"$uid": {
".write": "auth !== null && auth.uid === $uid",
".read": "auth !== null && auth.uid === $uid",
"username": {
".validate": "
!root.child('usernames').child(newData.val()).exists() ||
root.child('usernames').child(newData.val()).val() == $uid"
}
}
},
"usernames" : {
".write": "!data.exists() && auth!= null",
".validate": "newData.val() == auth.uid" <---- I can't get this too work
}
}
}
When setting username under $uid it checks the usernames index so username can only be written with a username not in use or one that has it's own $uid.
I only want data in which the value is the authenticated users uid and the key is the username. I can't quite get this to work. I suspect that I am using newData().val() incorrectly. My validate statement is failing.
I'd like to avoid using custom tokens, but I'm open to any suggestions. Thanks in advance.
Sorry if this explanation is too drawn out, this is my second post on StackOverflow.
Edit #2
I did some research from what I can tell and all I can find in docs talks about the need to use .child() before .val() but I need .child to take a variable instead of a set username.
The accepted answer will cause problems as soon as you want to allow people to change their usernames.
By slightly changing your database structure you can easily manage usernames in firebase. You can also allow users to change their username.
Database:
usernames: {
username1: {
owner: "userId1"
},
username2: {
owner: "userId2"
},
...
}
The following rules will:
stop any user changing/deleting another user's username
let a new user create a username
let a user change their username by deleting their old one and creating a new one
(split on newlines only for readability)
"usernames": {
"$username": {
".read": "true",
".write": "auth != null &&
(
\\ Allow user to delete only their own username
(data.exists() && data.child('owner').val() === auth.uid && !newData.child('owner').exists())
||
\\ Allow user to create a new username if the username is not taken
(!data.exists() && newData.child('owner').val() === auth.uid)
)"
}
}
This update will create a user:
const toAdd = {}
toAdd[`usernames/${username}`] = { owner: userId };
firebase.database().ref().set(toAdd)
This update will change a users username:
const update = {}
update[`usernames/${previousUsername.toLowerCase()}`] = null;
update[`usernames/${newUsername.toLowerCase()}`] = { owner: userId };
firebase.database().ref().update(update)
In your code you should also query the database to check if a username exists before sending the update.
Why I avoided the username: userId approach:
You say
Edit #2 I did some research from what I can tell and all I can find in docs talks about the need to use .child() before .val() but I need .child to take a variable instead of a set username.
I believe this is correct. For your usernames setup:
usernames {
"username1": userId1,
"username2": userId2,
...
}
This answer describes a method which sends the key information in the request:
ref.update({
_username: "username3",
username3: "userId3",
})
Then you can check if the username is in the database with a rule like
data.child(newData.child('_username').val()).exists()
However in your usernames setup, you would then overwrite the other usernames with this update. To avoid that you would need to set the new data at the path of the username, usernames/username3 = userId3. But you cannot set it like this as you are then back at the problem of not having the key to reference in the rule.
You would need to create a nonsensical structure something like:
usernames: {
username1: {
_username: "username1",
userId: "userId1"
}
}
So I chose the simpler, but unfortunately a slightly less beautiful username database setup I describe at the start.
Sorry if i'm late but i ran into a similar problem, i changed my usernames rule to the following which did the trick:
"usernames" : {
"$username": {
".write": "!data.exists() && auth!= null && newData.val() == auth.uid"
}
},
}

Resources