Skip to content

Instantly share code, notes, and snippets.

@katowulf
Last active April 11, 2024 12:07
Show Gist options
  • Star 58 You must be signed in to star a gist
  • Fork 13 You must be signed in to fork a gist
  • Save katowulf/4741111 to your computer and use it in GitHub Desktop.
Save katowulf/4741111 to your computer and use it in GitHub Desktop.
Firebase security rules for a simple chat room model
{
"chat": {
// the list of chats may not be listed (no .read permissions here)
// a chat conversation
"$key": {
// if the chat hasn't been created yet, we allow read so there is a way
// to check this and create it; if it already exists, then authenticated
// user (specified by auth.id) must be in $key/users
".read": "auth != null && (!data.exists() || data.child('users').hasChild(auth.id))",
// list of users authorized to participate in chat
"users": {
// if the list doesn't exist, anybody can create it
// if it already exists, only users already in the list may modify it
".write": "!data.exists() || data.hasChild(auth.id)",
"$acc": {
// for now the value is just a 1, later it could be a read/write/super privilege
".validate": "newData.isNumber()"
}
},
// timestamps recording last time each user has read this chat
"last": {
"$acc": {
// may only written by the authenticated user and if user is in $key/users
".write": "$acc === auth.id && root.child('chat/'+$key+'/users').hasChild($acc)",
".validate": "newData.isNumber()"
}
},
"messages": {
"$msg": {
// to write a message, it must have all three fields (usr, ts, and msg)
// and the person writing must be in $key/users
".write": "root.child('chat/'+$key+'/users').hasChild(auth.id)",
".validate":"newData.hasChildren(['ts', 'usr', 'msg'])",
"usr": {
// may only create messages from myself
".validate": "newData.val() === auth.id"
},
"msg": {
".validate": "newData.isString()"
},
"ts": {
".validate": "newData.isNumber()"
}
}
}
}
}
}
@quantuminformation
Copy link

quantuminformation commented Oct 19, 2020

The only other improvements I can think of is to bin the messages per month or another time period for bandwidth saving.

Also, we need a way to force 1 to 1 conversation as only having one instance. Think messenger and LinkedIn, you can only have one direct conversation with someone.

The last read thing with timestamps is genius.

@quantuminformation
Copy link

quantuminformation commented Oct 19, 2020

In terms of UI I'm thinking how to implement this.

The main problem I see is that there are two keys been generated for the first message. Normally the key is generated when we do a push to a path.

But for a new chat and on the first message (if we create them at the same time), we will have to generate two new IDs one for the actual chat and one for the first message. In this case, I think there are two steps 1st to create the chat and only then we add new messages.

I'm almost thinking a cloud function would be best for creating the chat which would also check to see if a previous conversation is created with the same users, only 1 to 1.

thoughts?

Update----
Previously one-to-one conversations were easy as I could use this code to generate the ID on the fly

export async function sendMessage(myUid, otherUid, content) {
    const conversationId = createConversationId(myUid, otherUid)
    const data = { content, timestamp: new Date().getTime(), authorUid: myUid }
    await firebase.database().ref(`conversations/${conversationId}`).push(data)
    return
}


export function createConversationId(uid1, uid2) {
    return uid1 > uid2 ? `${uid2}${uid1}` : `${uid1}${uid2}`
}

But I was told I could not rely on the length of the UID so that's why am in this thread

FURTHER THOUGHTS-----------
I dont' see a way for if conditions in the rules, so probably different chat paths for 2 way convo vs (2n + 1 ) convos, this would allow the following rules

2 way

checks to see that no other 2 way chat has the same two user

implementation ideas

A cloud function that looks at the chat creation and if there are only 2 users, it sets a property called one2one or smth than can't be changed.
The difficulty is for when a user wants to message another user, finding a good way to search for a previous conversation based on their 2 ids.

3 way group message

nothing special here, we just need a way to not allow group messages with the same 3 or more people , like facebook and linkedin

Last thoughts, I still think it would be quite safe to use the concatenation method for the key because I don't imagine FB is changing the length of your ID any time soon at least for the same project

@quantuminformation
Copy link

I've been thinking about this all day and have come to no elegant solution.

The only elegant solution is the concatenating method which I think firebase should support better; by locking uids format for any given project (it currently works very well using length 28 uids). Given that this is the only real-time usage case for firebase for me I'm wondering if firebase is the correct option here for this aspect of the social network I'm making.

@quantuminformation
Copy link

We decided on hashing users uid's, which means you can look up any existing conversation if you know the other persons uid's.

Each conversation also stores a list of the uids for their security rules, so even if you can guess the hash, you are protected.

@quantuminformation
Copy link

quantuminformation commented Oct 26, 2020

I would also recommend creating all timestamps with cloud functions to account for differences in time zones and computer clocks. Also if the user updates his last read value, that should also be server updated

@quantuminformation
Copy link

Please also note, that when writing to users you need to use update not set as this gotcha:

https://stackoverflow.com/questions/64544925/fireplace-update-allowed-but-set-fails-with-simulated-set-denied/64549981#64549981

@quantuminformation
Copy link

Also note

".write": "!data.exists() || data.hasChild(auth.id)",

should be

       ".write": "!data.exists() || data.hasChild(auth.uid)",

thank me later

@quantuminformation
Copy link

quantuminformation commented Oct 28, 2020

The only thing I would like to add to this is a way for any user in the chat to delete the entire conversation which is not possible as they would have to have had access to the whole conversation, which would break the more granular write rules.

Probably needs a cloud function with a custom token, if anyone has an idea how to do that.

@Carlos-ag
Copy link

How do you include a list of users in realtime database?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment