How to implement user based security in the firebase database? - firebase

This is the database for example:
"Messages" : {
"Message1" : {
"Uid" : "sampleid1"
"Text" : "hi"
},
"Message2" : {
"Uid" : "sampleid2"
"Text" : " hello"
}
}
I want only those users to read the messages whose uid is equal to the Uid field of Message#.
The structure of database given in firebase documentation(i.e. using user id based messages in the database where the node of each message represents the uid of the user who sent the message) doesn't achieve the goal of my project as I need to know the uid of the user who sent the message each time any user sends a message.
Therefore, please suggest the rules that would help me achieve my task as mentioned in this question
Also, when I applied certain rules on the above structure of database, I couldn't read any data because 'firebase rules are not filters'.
Please ignore the syntax and format of json written in above example as it is just for reference
Please help!

Structure your data so:
"messages" : {
"<receiver_uid>" : {
"msg_1" : {
"text" : "Hello world...",
"uid" : "<sender_uid>"
}
// more msgs for this receiver ...
}
}
the rules should be something like
{
"rules" : {
"messages" : {
"$receiver" : {
".read" : "auth.uid == $receiver", // only receiver shall read
".write" : "auth != false" // any authenticated user can write
}
}
}
}

Related

firebase realtime database calling set with new properties

i am using firebase realtime DB with my ionic app and using angularfire2. I have data stored like:
"users" : {
"230A3lKQqWh0TczOGP8sbtMVpuF2" : {
"email" : "xxx#gmail.com",
"isAdmin" : false,
"name" : "Santosh Yadav",
"pic" : "https://graph.facebook.com/xxx/picture"
},
"oSEYj0zrkhPCk9r7uwyOOkHcqe53" : {
"email" : "yyy#gmail.com",
"isAdmin" : true,
"name" : "Vik Kumar",
"pic" : "https://graph.facebook.com/xxx/picture"
},
"tl3uvseaBeWVYFCTwSngUqcSokX2" : {
"email" : "zzz#gmail.com",
"isAdmin" : false,
"name" : "Neeti Singhal",
"pic" : "https://graph.facebook.com/xxx/picture"
}
}
We ended up to store more user data like date of birth and date of aniversary and when we try to update it as below:
createUser(user:User){
console.log('start of create user')
var payload = {
"name": user.name,
"email": user.email,
"pic" : user.pic,
"dob" : user.dob,
"anivDate" : user.anivDate,
}
return this.db.object('/users/' + user.uid).set(payload).then(
(resp) => console.log("user created")
).catch(
(err) => console.log("issues creating user:" + err)
)
}
It fails with error:
ERROR Error: Reference.set failed: First argument contains undefined in property 'users.oSEYj0zrkhPCk9r7uwyOOkHcqe53.anivDate'
at validateFirebaseData (validation.js:113)
at validation.js:140
at forEach (obj.js:46)
I understand the error that it is unable to set a property anivDate that does not exist in my firebase realtime DB. This will fix if i manually add the property into the db manually. But that is not a practical fix. So, what is the solution here?
To define the fix: i am expecting to add a new property if that does not exist else just update it.
For a quick fix, use the || operator to set a default value on undefined elements. Usage:
var payload = {
"anivDate": user.anivDate || "1970-01-01",
...
};
This sets anivDate to user.anivDate if it exists, 1970-01-01 if it does not. (This works, but in my opinion it is better practice to not have a anivDate property on a user if no such value exists, and check for that value's existence when accessing the data.)
For a more scalable/reliable fix, use Firebase's realtime database triggers to add async validating or default values to your new columns.

Limit firebase query with equal-to using Polymer

I'm using Firebase with Polymer 2.0. My Firebase database contains users and groups. A user can be part of more than one group. A user can have different roles in different groups.
I want to list all groups where user is a member but I cant find a way to limit the firebase query to groups where user is a member. It's listing everything or nothing.
Database structure looks like this:
{
"groups" : {
"$groupId" : {
"members" : {
"$userId" : "true",
},
"name" : "Group Name A"
},
},
"users" : {
"$userId" : {
"groups" : {
"$groupId" : "true",
},
"settings" : {
"name" : "Linus"
}
}
}
}
Query looks like this:
<firebase-auth user="{{user}}"></firebase-auth>
<firebase-query
id="query"
path="/groups"
order-by-child="members/{{user.uid}}"
equal-to="{{user.uid}}"
data="{{accounts}}">
</firebase-query>
Expected result in this case would be to only display groups where $userId is true.

Inviting users to share a Firebase object

I have an app with checklists where I want to be able to share checklists with other users. Those users might not be registered yet in my app. Currently my database is structured as follows:
{
"checklists" : {
"-KQKfnuGEhhoSIXyFj71" : {
"admins" : [
{ "C9fdXO6jWTZIxzgbhy0K10TEBqx1" : true },
{"ZQKHTbTlVMQwnZzFkQUcJF84SaA3" : true }
],
"users" : {
"-KQKfnuGEhhoSIXyFj74" : {
"email" : "admin1#example.com",
"uid" : "C9fdXO6jWTZIxzgbhy0K10TEBqx1",
"active" : true,
"name" : "Moses"
},
"-KQKfnuGEhhoSIXyFj75" : {
"email" : "admin2#example.com",
"uid" : "ZQKHTbTlVMQwnZzFkQUcJF84SaA3",
"active" : true,
"name" : "John"
},
"-KQKfnuGEhhoSIXyFj76" : {
"email" : "user1#example.com",
"uid" : "1ZxxYFGzoCPIc7Am07GVRxSN7xT2",
"active" : true,
"name" : "Kate"
},
"-KQKfnuGEhhoSIXyFj77" : {
"email" : "user2#example.com",
"active" : false
}
},
"data" : {
"task1" : "hello",
"task2" : "good bye"
}
}
}
}
My bolt file is as follows:
// admin can do both read/ write to the while checklist
path /checklists/{checklistId} {
read() { isChecklistAdmin(checklistId) }
write() { isChecklistAdmin(checklistId) }
}
// users of checklist can do read/write on data only
path /checklists/{checklistId}/data {
read() { isChecklistUser(checklistId) }
write() { isChecklistUser(checklistId) }
}
// users of checklist can add/ remove themselves from the list by updating active flag
path /checklists/{checklistId}/users/{checklistUserId} {
// all checklist users can read other users
read() { isChecklistUser(checklistId) }
// only admin (implicit) or the user can accept/reject (active) invitation or update their details
write() { isChecklistUser(checklistId) && ref.parent().email == auth.email }
}
// is authenticated user in the list of checklist admins?
isChecklistAdmin(checklistId) { isSignedIn() && root.checklists[checklistId].admins[auth.uid] != null }
// is authenticated user in the list of checklist users (test email ==)
isChecklistUser(checklistId) { isSignedIn() && root.checklists[checklistId].users['*'].email == auth.email }
isSignedIn() { auth != null }
The flow I had in mind was:
Admin adds a node under users which has the email of the invited user
Admin sends an email invite (I would have liked to use Firebase invites but I don't see how to integrate it in this scenario - would love suggestions).
User registers/ logins and updates active flag to true
My question: if you look at the isChecklistUser function, you will find I am using a "wildcard" ("*") since I do not know what that value is. Admin cannot set it at invitation since the user might not be registered yet.

Firebase Wildcard Paths For Security Rules Not Working? [duplicate]

This question already has answers here:
Restricting child/field access with security rules
(3 answers)
Closed 6 years ago.
I'm using the Objective-C API for Firebase to fetch data and am able to do so when my security rules (set via the Firebase online dashboard) don't utilize any wildcard paths, e.g.:
{
"rules": {
"user" : {
".read" : true,
".write" : true
},
"users" : {
".read" : true,
".write" : false
}
}
}
But when I try enact what should be identical security rules using wildcard paths and fetch objects, the completion handler never executes, e.g.:
{
"rules": {
"user" : {
".read" : true,
".write" : true
},
"users" : {
"$userId" : {
".read" : true,
".write" : false
}
}
}
}
I used the Firebase documentation at the following URL and can't figure out what I'm doing wrong: https://www.firebase.com/docs/security/quickstart.html
I don't think the problem is Objective-C specific, but just to be thorough I'm using the method -[FQuery observeSingleEventOfType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot) { }] to fetch my data.
Update: Here's the output of po query for the particularly FQuery I'm using to debug:
(/users {
ep = 0;
i = hidden;
sp = 0;
})
Update 2: Here's my data structure, in case that is relevant:
{
"user" : {
"HhMeloQDY4" : {
"info" : {
"name" : "Anita Borg"
}
},
"QxnjCNOj3H" : {
"info" : {
"name" : "Charles Babbage"
}
},
"zeNalC4ktf" : {
"info" : {
"name" : "Beyoncé"
}
}
},
"users" : {
"HhMeloQDY4" : {
"hidden" : false
},
"QxnjCNOj3H" : {
"hidden" : false
},
"zeNalC4ktf" : {
"hidden" : true
}
}
}
Update 3: Here's my Objective-C code for how I create my FQuery object:
Firebase *firebase = [[Firebase alloc] initWithUrl:#"https://<my-app-name>.firebaseio.com"];
[[firebase childByAppendingPath:#".info/connected"] observeEventType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot) {
BOOL isConnected = [snapshot.value boolValue];
// broadcast whether app is connected to Firebase
}];
Firebase *directory = [firebase childByAppendingPath:#"users"];
FQuery *query = [directory queryOrderedByChild:#"hidden"];
query = [query queryEqualToValue:value];
[query observeSingleEventOfType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot) {
// data successfully retrieved from Firebase
}];
You have added read access at the path /users/specific_user_id/ but you're attempting to read at the path /users/, which has no read access allowed.
You'll need to provide access to the path you are attempting to read, not just a subset of its children. See security rules are not filters.
Edit: Just adding some ObjC code to clarify
With the query presented
Firebase *directory = [self.myRootRef childByAppendingPath:#"users"];
you are querying the nodes directly inside the users node. However, if you review the structure, what's inside the users node is not queryable as there are no rules directly under /users, where I have commented.
"users" : {
//OH NOES! There are no rules here!
"$userId" : {
".read" : true,
".write" : false
}
Your rules are inside the $userId, which represents and applies to that parent only
"$userId" : {
//these rules *only* apply inside each userId.
".read" : true,
".write" : false
}
So with your structure, this query would work and it would query the content inside users/HhMeloQDY4 only.
Firebase *directory = [self.myRootRef childByAppendingPath:#"users/HhMeloQDY4"];
So the end result is that you need to assign the rules directly under the /users node that will allow you to query for content within it's child nodes.
"users" : {
".read" : true,
".write" : false
"$userId" : {
}
This would allow you to read each node under users node ($userId and it's children) but not write to them.

How to set a "primary key" in Firebase?

This is the schema I plan to have for my message board app where users can create their own message boards:
{
"boards" : {
"jane-board" : {
// meta information like who created this board
},
"john-board" : {
// meta information like who created this board
},
...
},
"jane-board" : {
// data
},
"john-board" : {
// data
}
}
What would be the rule to ensure that "boards" cannot contain two "jane-board"?
I tried writing a rule but it fails:
{
"rules" : {
".read" : true,
"boards" : {
".write" : true,
"$board_name" : {
".validate" : "!newData.parent().hasChild($board_name)"
}
}
}
}
Since you are using the board's name as the key to store it under, there is already a guarantee that each board name can exist at most once.
It is not entire clear what you're trying to accomplish. But if you are trying to prevent a board's data from being overwritten, you can accomplish that with:
{
"rules" : {
".read" : true,
"boards" : {
"$board_name" : {
".write" : "!data.parent().hasChild($board_name)"
}
}
}
}
Changes for your rules:
I removed the ".write": true from boards. With that rule in place everyone can read all boards, since you cannot take permissions away on a lower level.
I changed the rule to a ".write" rule, since it feels more like preventing a write than validating structure
I check whether the new board already exists in the current data. You were checking in newData, but that doesn't make any sense: the new board will always exist in the new data.

Resources