Adding Security to Firebase to prevent the insertion of additional data - firebase

I have a field within Firebase called 'pending_members' which contains a list of members pending permission to be granted by an 'owner', as such, 'pending_members' requires the following rules:
The current user can only add themselves (uid)
The current user can remove only themselves (uid) from the list
The 'owner' can remove any member from the list
Only the 'owner' can read the list
I've tried various security rules but seem to miss many corner cases, for example, a user is given write access because the data contains their uid but they can then submit someone else's uid along with this.
Can anyone suggest appropriate rules for this situation? Many thanks
"pending_members" : {
".write" : "auth !== null &&
// The user is authenticated AND
(newData.child(auth.uid).exists() ||
// The new data contains either the current user's id OR
(!newData.exists() &&
// There's no new data (a delete operation) AND
data === auth.uid))",
// The old data is the current user's id
"$member" : {
".validate" : "newData.isString()",
"$other": { ".write": false, ".read": false }
}
}
Edit:
Structure Example:
users ->
personal_data ->
email (user email address)
first_name (user first name)
last_name (user last name)
networks_index ->
networks ->
members (list of uids of users linked to the network)
owner (uid of the owner/primary user)
pending_members (list of uids of users wishing to link to the network)
Data Example (image)

Complicated structure you have but i will give it a try:
Keep in mind standard value for read and write is false.
{
"rules": {
"networks": {
"$networkid": {
//Give read and write access to the owner of the network
".read": "auth != null && "root.child('networks').child($networkid).child('owner').val() == auth.uid",
".write": "auth != null && "root.child('networks').child($networkid).child('owner').val() == auth.uid",
"pending_members": {
"$uid": {
//Give members write access to their own node inside pending_members
".write": "auth != null && auth.uid == $uid",
//Use validate to check if the value is a bool or emty(removal)
".validate": newData.isBoolean() || !newData.exists()
}
}
}
}
I have only concentrated on the pending_members here, I hope that is enough and it is clear enough. If it doesn't work i suggest testing each rule seperatly to see which one is causing a ploblem so I (or someone else) can help fix it.

Related

can't create new children in firebase database aftter setting the rules

I am using firebase database in my app. I have set the rules to allow normal users to create new childs in the node but I found that it's refused.
this is the writing rules of the node :
".write":"auth.uid == \"DFhNb28506Y345CpJ3Ye7DQNn713\" || ((newData.exists() && !data.exists()) || auth.token.email == data.child(\"userEmail\").val())",
I think that newData.exists() && !data.exists() should allow users to write in the database but this doesn't happened
this is the rules of the users node :
"users":{
".write":"auth.uid == \"DFhNb28506Y345CpJ3Ye7DQNn713\" || ((newData.exists() && data.child(\"userEmail\").val() != null) || auth.token.email == data.child(\"userEmail\").val())",
".read": "auth != null"
}
The database strucutre is like that :
-users
-user1
-userName, userEmail ....
-user2
-userName, userEmail .....
when a new user sign up in the app he should be allowed to push his data in the database
this is the database structre :
Ok, I think you're creating extra validation steps that aren't needed.
First
With ".read": "auth != null" on your users root, each user is able to access other user's data, so we should address the access for each user individually.
Second
If you just want to allow users that are authenticated to write and read its own contents, you can remove these extra ((newData.exists() && !data.exists()) and auth.token.email == data.child(\"userEmail\").val()) steps.
Tip: this ((newData.exists() && !data.exists()) comparison means exactly: Write here if you're sending anything but there should be nothing written in this requested "path". You should reflect on the need of this, as I don't know your exact use cases.
Also, I would guess the hardcoded UID you're requesting is of an Admin you've created - I wouldn't recommend this, please read more about user roles on this answer.
To clarify, I think your rules structure should be something like this:
{
"rules": {
".write":"auth.uid == \"DFhNb28506Y345CpJ3Ye7DQNn713\",
".read": "auth.uid == \"DFhNb28506Y345CpJ3Ye7DQNn713\",
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
}
}
}

Search among the documents the user has permission to read in firebase

I have a collection of documents. Each document has child uid which is a reference to the owner. Exemplary document:
{
"uid": "slfjs092320i3jf023jf",
"content": "Example"
}
I want to store them under /documents collection and allow only users with matching uid to store and retrieve them. I created the following rules:
{
"rules": {
"documents": {
"$documentId": {
".read": "data.child('uid').val() === auth.uid",
".write": "newData.exists() && newData.child('uid').val() === auth.uid || data.exists() && data.child('uid').val() === auth.uid",
".indexOn": ["uid"]
}
}
}
}
Unfortunatelly when I want to retrieve user's documents I receive permission denied. To retrieve documents I use:
export default getUserDocuments = () => {
const userUid = firebase.auth().currentUser.uid;
return firebase.database()
.ref('/documents')
.orderByChild('uid')
.equalTo(userUid)
.once('value');
};
How do I query user's documents? I guess the permission denied is related to read restriction required to perform the query.
Firebase Database enforced read permission when you attach a listener. To be able to read from /documents, you will need to have read permission on /documents. Since that isn't the case with your security rules, the listener is rejected.
This may be counter-intuitive initially, and means that security rules cannot be used to filter data in the way you are trying. This is known as rules are not filters in the documentation, and has been the topic of many previous questions. I recommend you check out some of those, and report back if you have more questions.
Have a look on query based rules.
Rules are not filters, as Frank said, and he is right, but you can make some queries to works and achieve something similar to what you were looking for with this kind of stuff :
baskets": {
".read": "auth.uid != null &&
query.orderByChild == 'owner' &&
query.equalTo == auth.uid" // restrict basket access to owner of basket
}
And then, this will work :
db.ref("baskets").orderByChild("owner")
.equalTo(auth.currentUser.uid)
.on("value", cb)
And this, won't :
db.ref("baskets").on("value", cb)

swift Firebase stop signup process

I want to stop the signup process if the username exists. I don't want to create Email\Password (Authentication) and other info if the username exists
database rules:
{
"rules": {
"users": {
".read": "(auth != null)",
".indexOn": ["username"],
"$uid": {
"$username":{
".write": "(auth != null) && !data.exists() || newData.exists()"
}
}
}
}
}
I have added !data.exists() in .write , but still it duplicate usernames.
And then I added ".validate" to make username atleast 3 characters, to see if the signup process gonna fail if its longer, but it created email\password and other stuff but didn't wrote any data in database root\users\
After registered new user with email\password I got this error, Which means username couldn't be more than 3 characters. How to stop the signup process and show alert message at this situation?
[Firebase/Database][I-RDB03812] setValue: or removeValue: at /users/jt94ARqOoodtU3kYp3W1MtlUZ4m1 failed: permission_denied
Please help me if you could fix the problem.
Your simplified rules are:
{
"rules": {
".write": "(auth != null)",
"users": {
"$uid": {
"username": {
".write": "auth != null && (!data.exists() || !newData.exists())",
So you first grant any authenticated user write access on the root of the data and subsequently say that a user can only write if there is not data a the current location.
That won't work: once you grant permissions at a certain level, you cannot take that permission away on a lower level.
The Firebase documentation describes this as Read and Write rules cascade:
Child rules can only grant additional privileges to what parent nodes have already declared. They cannot revoke a read or write privilege.

How to extract data from node variables to validate user roles on firebase

We are adding authentication roles to firebase.
our messaging structure is like this:
/users/<userId>/rooms/<roomId>/
roomId is custom string which concatenating both users(sender-reciver):
for example:
/users/4hkjheqrnv501eltbg1px/rooms/4hkjheqrnv501eltbg1px-638red8osipedem08j1a6/
i want to give write access to this room only for usersId's
4hkjheqrnv501eltbg1px or 638red8osipedem08j1a6 (which the roomId specifing)
How can I actually extract the userId's from the roomId variable and check if one of them is within the auth object.
I tried it this way but didnt work:
"rooms": {
"$roomId": {
".write": "auth != null && auth.uid.contains($roomId)"
}
so write access will be granted to this room only for auth users 638red8osipedem08j1a6 4hkjheqrnv501eltbg1px
Any ideas how to modify this?
I think you need to split your room address to array and find if the user id is in the array like:
"rooms": {
"$roomId": {
".write": "auth != null && $roomId.split('-').indexOf(auth.uid) > -1"
}
}

Firebase private chat schema and rules

I am trying to setup private chat abilities in an app that I am working on and I'm having a bit of trouble wrapping my head around denormalizing the data/setting up the rules properly.
After doing some reading, I realize that rules are all or nothing so using rules to filter is not an option.
I've sketched out my basic idea on paper, and have pasted it below. Basically there would be two main routes, users and chats.
Users would just be a keyed list, which each key matching an authenticated user. Then inside each member of the list I would just have each chat that the said user is in listed as a key.
For the chats route I'd have a list of all of the chats.
Now for the rules.
Users would only be able to read their data in the list where the key matched their uid. For the write i'm less confident. I'm thinking I have to let anyone with authentication write, otherwise the user starting the chat could not notify others of the new chat by playing the chat id in their chat list in the users route.
For the chats rules both read and write would only be allowed if the user is authenticated and the chat key is located inside their data in the user route.
Does the seem like I'm going in the right direction?
users:{
user1:{
chat1: true,
chat2: true
...
},
user2:{
chat1: true,
chat3: true
....
}
}
chats:{
chat1:{
lastUpdate: timestamp,
messages:{
0:{
from: user1
to: user2,
message: some message
}
...
}
}
}
rules:{
.read: false,
.write: false,
users:{
$user_id:{
.read: auth != null && $user_id == auth.uid,
.write: auth != null //not sure here as other users need to write here if the start a new chat
}
},
chats:{
$chat_id: {
.read: auth != null && root.child('users').child($chat_id).contains(auth.id),
.write: auth != null && root.child('users').child($chat_id).contains(auth.id)
}
}
}
I've been playing with this more, so here is one option (by no means am I suggesting this is the best way to do it)
Rules:
{
"rules":{
".read": false,
".write": false,
"users":{
"$user_id":{
".read": "auth != null && $user_id == auth.uid",
".write": "auth != null" //not sure here as other users need to write here if the start a new chat
}
},
"chats":{
"$chat_id": {
".read": "auth != null && root.child('users').child(auth.uid).child('chats').hasChild($chat_id)",
".write": "auth != null && (root.child('users').child(auth.uid).child('chats').hasChild($chat_id) || !data.exists())"
}
}
}
}
then for users I have a structure like this:
users:{
someUserId:{
chats:{ //embedded a second level so I can save firebaseObj.someUserId to get the keys more easily
someChatId: true //and repeat for each chat
}
}
}
Chats are like this:
chats:{
someChatId:{
//chat data
}
//more chat objects
}
I wouldn't be surprised if there is a much better way to do this, but at least this may be a start for those who are stuck. I'll try to remember to update this if/when I get a better solution.

Resources