restricting access to firebase database to users within a set - javascript

I wrote a simple chat application a while back to help me learn node and socket.io, I have recently been looking into firebase as a platform for an app/website, and I am running into an interesting problem I can't solve. Within the app there will be a chat app, there will be channels and all that good stuff, the hard part is there will be private chat, where 2 users can talk privately. The plan is to store the chat in a firebase database. Now I can easily restrict access to firebase databases based on if a user is authenticated, and even restrict user profile access to authenticated users and the user that owns that profile, but I am trying to figure out how to restrict access to the "private chat" children of the "chat" database, to only the 2 users that are in that conversation.
I am thinking the database would look something like this...
{
"chat": {
"channels": ['topics', 'current', 'blah', 'blah', 'blah'],
"{PRIVATE_CHAT_UID_GOES_HERE}": {
"users": ["{USER_ID_1}", "{USER_ID_2}"],
"messages": [{"from": "{USER_ID}", "message": "Hi there"},{...}]
"createdOn": "DATE GOES HERE"
},
"{PRIVATE_CHAT_UID_GOES_HERE}": {
"users": ["{USER_ID}", "{USER_ID}"],
"messages": [{...}, {...}],
"createdOn": "DATE GOES HERE"
}
}
}
Then I would restrict access to the child(private chat id) to only the users that are in the "users" array. That way no one can read or write to that particular chat, unless they are in that particular chat. I just have no idea how to go about it.
I know you can do things like
".read": "auth !== null && auth.uid = $uid"
But I don't think that would be applicable since it limits usage to the owner of the uid, and the uid would be automatically generated when I add a child to "chat" to start a private chat between users.
Is something like this even possible, or is there some better way to structure the data that would allow an easy restriction of access to only the 2 users that are part of the conversation?
I am trying to avoid having a node.js server sitting around just verifying if a user is in a chat, it seems like a pointless overhead, when you can list the database and handle auth directly from the database. I am more than happy to provide code samples of what I have, though I don't know that they are relevant. Thank you in advance for any help.

Your first problem is that you're storing the users in an array. An array is an ordered collection that can have duplicate values. In your scenario you don't want duplicate values, and most likely the order doesn't matter. In such cases you should use a set data structure, which in Firebase is modeled as:
"users": {
"{USER_ID_1}": true,
"{USER_ID_2}": true
}
Now you can restrict read access to the chat room to its members by:
{
"rules": {
"chat": {
"$roomid": {
".read": "data.child('members').child(auth.uid).exists()
}
}
}
}
Be careful mixing different data types in the same node. It's often best to keep each entity in its own top-level node, relating the different types by their key. For example, I'd separate the room's messages, members, and other metadata:
chat
rooms
<roomid1>
createdOn: ....
.... other metadata for the room
roomUsers
<roomid1>
user_id1: true
user_id2: true
roomMessages
<roomid1>
<message1>: { ... }
<message2>: { ... }
<message3>: { ... }

Related

Auth0: How to add custom properties to UserObject?

Hello dear StackOverFlow community,
i don't know much about Auth0 and need help. And I wonder how to add my own properties when creating a user? just for info i have connected Auth0 to my own MongoDB atlas database.
i want to add a customId when creating a user, and this should be a 16-digit random number.
And my userObj should then look something like this:
{
_id: ObjectId("5f720126054c87001662a138"),
connection:"MongoDB"
client_id:"zoClZ3gZE56iwblHXQ1vgwEcLfYr81Bx"
email:"gustek#gustek.com"
username:"gustek"
password:"$2b$10$dE69gGsDqVfWtmnXZ6EaKetILUmEju8N9PVjtDpgzzAp4jYNQbe8G"
tenant:"dev-test"
email_verified:false
customId:"9829539769841530"
}
I mean I found something in the documentation but I do not know how to implement it:
https://auth0.com/docs/users/set-metadata-properties-on-creation
Do I have to do it over this surface?
As I said before, I have no idea how I could achieve this. I am grateful for every answer!
You can use an app_metadata property in the user object to store data that is not part of the normalized user profile. This would include a custom UUID like you described here.
The user object would look like this:
{
client_id: "<ID of creating client (application)>",
tenant: "<name of creating Auth0 tenant>",
email: "<email address for the user>",
password: "<password for the user>",
username: "<name associated with the user>",
user_metadata: {
"language": "en"
},
app_metadata: {
"custom_id": "1234567890"
}
}
While user_metadata and app_metadata are optional, if supplied, they do not need to be stored in the legacy identity store; Auth0 automatically stores these values as part of the user profile record created internally. These values are (optionally) provided as a reference: their contents potentially being influential to legacy identity creation.

Firebase database security for new users

Is there a way to check the values using rules that are initially set on user creation?
I have perused the firebase docs to no avail.
When the function createUserWithEmailAndPassword is called, I then create some values in the database, for example:
"users": {
"ht35resf435dwe3rfdw": {
"is_premium": false,
"display_name" "John",
"last_login": 15353723826
}
}
The problem I am facing is: is_premium: false is part of the front end code and I am worried that a user could somehow change this to is_premium: true.
I can't figure out a way to check that it is initially set to false on creation.
P.s I could be going about this all wrong, I am a junior so I would appreciate any and all pointers.
To only allow a value to be set to false, you can use a validation rule:
{
"rules": {
"users": {
"$uid": {
"is_premium": {
".validate": "newData.isBoolean() && newData.val() == false"
}
}
}
}
}
The above will simply only allow false to be written by any client. When you're writing using an Admin SDK however, those writes bypass these security rules. So you can use the Admin SDK to mark premium users.
you can just change the security rules as below so that the user won't be able to make changes.
{
"rules": {
"foo": {
".read": true,
".write": false
}
}
}
I just started using Firebase so I cannot provide much information. If you want to start learning it there is a great series you can watch. Also, I would suggest using Cloud Firestore instead of the Realtime Database because the ladder is older and has fewer features. Changing the security rules on your database will let you fiddle with which users can edit data. What you need to do is make the premium default to false or even not exist in the database and then create a javascript function to create or change it to true in the database.
EDIT:
Ajith Naruto's would work but it would also disallow all writing to the database from the web app.
EDIT 2:
Frank van Puffelen's should work I would go off of his answer.

Firebase realtime database rules - Allow multiple user access to list all messages

I would like to give multiple users access to fetch a collection of "messages" they have access to in Firebase Realtime database. The database fetch would read "/messages" and return a collection of all messages the user has access to. Database structure looks like this:
"messages" : {
"-L123456789": {
"access": {
"author": "user-one-id-987654"
"assigned-user-id-1234": "assigned-user-id-1234"
}
"data" : {
"theData": "Hello world!"
}
}
}
I have created the following rule:
{
"rules": {
"messages": {
"$message_id": {
"data": {
".read": "
//if author or assigned user
data.parent().child('access').child('author').val() === auth.uid ||
data.parent().child('access').child(auth.uid).exists()
",
".write": "false"
}
}
}
}
However, I am not able to get a collection of all messages where I am listed as author or assigned user.
What rule would allow a user listed as "author" (user-one-id-987654) or "assigned user" (assigned-user-id-1234) to get a collection of all messages they have access to by simply reading the "/messages/" database path?
I am guessing a rule in the root of "messages" might be the answer?
I have tried the below rule - it grants access to all authenticated users - but I wish to only return a collection where the user is listed as "author" or "assigned user".
{
"rules": {
"messages": {
".read": "auth.uid !== null"
}
}
}
Kind regards
/K
Firebase server-side security rules can not be used to filter data. When you attach a listener to the database, the server checks if that listener is guaranteed to always meet the rules (no matter what the data). If it doesn't meet the rules, the listener is rejected right away.
So if you attach a listener to /messages, the server checks if you have read permission to /messages. And since you don't, it rejects the listener.
If you want to allow the user to read messages of which they're the owner you'll need two things:
A query that only retrieves the messages that the user owns.
Security rules that ensure only that query is allowed.
For more on this, see the Firebase documentation on securely querying data and the blog post introducing this feature.

Firebase does "read: true" rule allow to see users emails?

I have a firebase realtime db with the following rules structure:
{
"rules": {
"users": {
".read" : true,
"$user_id": {
".write": "$user_id === auth.uid"
}
}
}
}
does it allow anyone to see the db users' emails even if they only sign up using socials (google, fb)?
how can i avoid it in case?
I've set read : true, cause i create dynamic links which on open through routing they show certain data of certain users to an unregistered user and should remain like that.
EDIT: to let you better understand the process, what i'd need to set are rules so that users can sign up, only signed in users can write data, but if they create a link (with one of their items id contained in the link) anyone can open that link and read the item.
Here is the tricky part, at the same time non registered users shouldn't be allowed to read users emails (the email address saved by firebase upon social login, i do not programmatically save email address in the db).

Firebase Cloud functions to avoid client side work is always worth?

I'm recently working with Firebase Cloud functions to delegate lot of work from my client side to the server, reducing the data cost for the user. But recently I wondered if it's worth it or not, or maybe a better database structure could fix it.
I have a social app where the user can workout and post their results, you can follow users and all kind of "typical" social media stuffs. Well, my problem appear when I want to implement pagination retrieving the last X workouts that I should show to each user on their feed.
My question is : How expensive could be update from 1-1000(worst case) fields on the database on a common event trigger on Firebase Cloud functions. It's enough expensive at client side to look for avoid it and look for better ways talking about performance even if it's more expensive at client side?
I will explain it looking at my example:
Database Structure
"privateUserData" : {
"user1" : {
"messagingTokens": {
"someToken": true,
"someToken2": true,
},
"accountCreationDate" : 1495819217216,
"email" : "abcd#gmail.com",
"followedBy" : {
"user2": true,
"user3": true,
},
"following" : {
"user2": true,
"user3": true,
},
"lastLogin" : 1498654134543,
"photoUrl" : "photo.png",
"username" : "Francisco Durdin Garcia"
},
},
"publicUserData": {
"user1": {
"username": "someUserName",
"followersCount": 5,
"followingCount": 1,
"photoUrl" : "someUrl"
}
...
},
"workouts" : {
"workout1" : {
"likes": {
"user1": true,
"user2": true,
...
},
"followers": {
"user1": true,
"user2": true,
...
},
"comments": {
"comment1": {
"owner": "user1",
"content": "somecomment",
"time": 1493153530311,
"replies": {
"reply1": {
"owner": "user1",
"content": "somecomment",
"time": 1493153530311,
}
}
}
}
"authorUid" : "user1",
"description" : "desc",
"points" : 63,
"time" : "00:03",
"createdAt" : 1493153530311,
"title" : "someTitle",
"workoutJson" : "workoutJsonDataHere"
}
}
To be able to do that query I should do individual queries for each user I follow:
The problem is that I can do a "global" query and limit it to just X dataSnapshots. I can just filter few workouts for each individual query:
mDatabase.child("workouts").orderByChild("authorId).equalTo("userIFollow").limitToLast(10)
This query will return me a filter applied just for one userIFollow it's not possible to do it over all of them, so I have three options:
1. Create a table which stores relation between usersId and workoutsId visible by them with a timeStamp value. But I should
keep track of this values thought a Firebase cloud function, and
obviously maybe I follow an user with Thousands of workouts I my
cloud functions would need to copy ALL OF THEM to the right
reference.
This was the way I wanted to go, but I don't know if it's
the proper way talking about client side cost.
2. I can add a lastActivityTimeStamp on publicUserData and filtering by that retrieve just a few workouts of the last users with activity, growing this query with a pagination too.
3. Finally I can always retrieve all the workouts from this user and filter on client side, this will be expensive just one, because later the cache will do everything easier.
This are the ways I found to resolve my problem, and my question is still how expensive and useful are Firebase Cloud functions to copy large amounts of data with common triggers.
From the way you worded your question, you seem familiar with the Database Cloud Functions for Firebase and it also seems that 'workouts' is your payload (the biggest chunk of data that you don't want to download repeatedly).
I would recommend the following approach based roughly off how GitHub's API works.
Prerequisites
In your /privateUserData/{user} data, you seem to have the list of followed user IDs (at /privateUserData/{user}/following). To make your queries simpler, I'd recommend implementing a list of workout IDs authored by that user (under something like /publicUserData/{user}/authorOf).
Implementation
I'd recommend building a HTTP Cloud Function, at say https://FUNCTION_URL/followedWorkouts. When called you would generate a list of workout IDs for a given user by checking who they follow and then getting the list of workouts authored by each followed user and return them as one array. To identify the user, you could pass in their ID using a GET parameter such as ?user=<someUserId> or through some form of authentication. How you go about it is up to you.
The function should return data in the following (or similar) format (in this case I'm using JSON):
[{"id": "workoutId1", "lastMod": "1493153530311"}, {"id": "workoutId2", "lastMod": "1493153530521"}, ...]
id is the the workout ID.
lastMod (short form of last modified) is the last time that workout's data was updated (from {workoutId}/lastModificationDate). See the 'caching' section below.
Filtering
I'd also implement the following 'filters' on the Cloud Function:
Since (?since=<someTimeStamp>): will return workout IDs that have been modified since that timestamp. (Say you downloaded some information at some time, T, you would then set since=T to then only receive workouts changed after that time.
Max (?max=X): will return the X most recent entries.
Start At (?startAt=X): will return the most recent entries starting at the index X (I'd make it a 1-based index).
So if you wanted to grab the 10 most recent entries, you could call https://FUNCTION_URL/followedWorkouts?max=10 which would give you the IDs for the 1st-10th most recently updated workouts. For the next 'page' of entries, you would call https://FUNCTION_URL/followedWorkouts?startAt=10&max=10 which would give you the 11th-20th most recently updated workout IDs.
Caching
As each workout is a payload, it doesn't make sense to download the multiple times. I would recommend caching this data to prevent this. In the response I suggested above, the field lastMod (last modified) allows you to check if a locally cached version needs updating. How you go about this, is yet again up to you.
Extending
If you need more of these paginated feeds, you could name the function more generally such as https://FUNCTION_URL/feeds and pass in the feed type as a parameter https://FUNCTION_URL/feeds?type=workouts. You could use this for things like followers, following, comments, etc.
Feel free to reach out if you need some more information.

Categories