Firebase realtime database rules for a chat app - firebase-realtime-database

Trying to put my question in its general concept so that your reply will stay relevant to all the ones who are stuck firebase rules for a chat app.
My chat database is a simple one and has 3 nodes: Messages / Connections / Users as below:
Database
-Messages
--message :
--$uid
...
-Connections
--Connection1
--Connection2
--ConnectionX
...
-Users
--UID1
--UID2
Here's what I need to proofread pls for my realtime database rules:
{
"rules": {
"users": {
"$uid": {
".read": "auth != null" && "$uid/current_page" == "3w_stringofmywebsite/*",
".write": "auth != null" && "$uid/current_page" == "3w_stringofmywebsite/*"
}
// .read & .write to authenticated users & variable $uid/current_page= "3w_stringofmywebsite/*" (allow access from requests from all pages from my web site only)
}
"connections": {
"$uid": {
".read": "auth != null" && root.child(‘users’).child($uid).child(“current_page ") == "
3 w_stringofmywebsite/*"
}
// .read to authenticated users & variable from another path “users/$uid/current_page” = "3w_stringofmywebsite/*" (allow access from from all pages from my web site only)
}
"messages": {
".read" : "auth != null" && root.child(‘users’).child($uid).child(“current_page") == "3w_stringofmywebsite/*" ,
".validate": "auth!=null" && data.child('msg').val ().length <= 140 && data.child('msg').isString"
}
// .read to authenticated users & variable from another path “users/$uid/current_page” = "3w_stringofmywebsite/*" (allow access from all pages from my web site only)
// .write to authenticated users & variable “msg” is a string & < 140 characters
}
Thank you for your generous correction to the code above so that it serves all other newcomers.

Thank you dear Frank,
the syntax of commands has many errors (tried many times to do the corrections but the list is long) and I don't want to fill the ticket here with beginners mistakes.
=> what I need is someone who masters the syntax and relieve me from bumping my head at each step in each command!
*For the "users" node:
Read: right to only authenticated connections and the ones that are coming from my website only (as the chat tool is on this website)
Write: right similar to above.
For the "connections" node:
Read to authenticated users & the connections that are coming from my website [there's a variable "current_page" from another path “users/ "ABC"/current_page” = "3w_stringofmywebsite/" that I can use to match the string of my website as a test
NB: "ABC" above is a string auto generated by the node and I don't know how I can point to the variable "current_page" below it since it keeps varying from one node entry to another!
*For the "messages" node:
Read: privilege to authenticated and again check if the variable “users/ "ABC"/current_page” is from my web site
Write: privilege to authenticated and the message is a string < 140 characters.
Appreciate your support in this.
Thank you.

Related

Firebase realtime database - Wildcard rules

I'm a bit confused about firebase rules. This is my realtime database. Each node inside "1" is created using the firebase unique id of the user. And in the user's node there is a list of objects.
The objective is for the user to be able to create this node if it doesn't exist, and allow the user to read/write only inside this node.
I tried this but it doesn't work. I get permission error.
{
"rules": {
"1": {
"$key": {
".read": "auth != null && auth.uid == $key",
".write": "auth != null && auth.uid == $key"
}
}
}
}
Note: In the future there will be other parent nodes ("2","3" etc) So it is important to keep the "1". Also in case it matters I am using firebase anonymous sign in.
I appreciate the help.
UPDATE:
I retrieve the installationId like this:
Task<String> getIdTask = FirebaseInstallations.getInstance().getId()
and access the database like this:
FirebaseDatabase.getInstance()
.getReference()
.child("1")
.child(installationId)
Trying to access the database using above code gives this:
Listen at /1/cKYZwWrlRmSof79rtfuX82 failed: DatabaseError: Permission denied
SOLUTION:
I just realized the magnitude of my mistake. To retrieve the userId I was using
FirebaseInstallations.getInstance().getId()
instead of this which is what firebase sees as userId:
FirebaseAuth.getInstance().getCurrentUser().getUid();
Using the later one solved the issue.
Unless you'd add another node, that actually links the UID with your user ID, how shall it know about it? I'd suggest to reconsider the structure and get rid of that superfluous node altogether; just use the UID. It's not that it wouldn't be possible, to lookup values by UID ... but it might be an unfortunate database design, which ignores the given environment.

realtime firebase database read data from request

So I'm have 3 collections
1- cases
3- owner (inside cases collection)
2- invitations
in normal senario when user add invitation I check if user that tray this action is owner or not by , take caseId from newDate and uid from auth.uid and compare these data by exist in cases collection
but it's always give me permission denied.
// Any one have cases
"cases":{
".write" : "auth.uid != null"
// Store id of owner only when case created first time
"owner" : {
".write": "!data.exists()"
}
}
//only Owner of case can add invitations
"invitations":{
".write" :"root.child('cases').child(newData.child('caseId').val()).child('owner').child(auth.uid).exists()",
},
the data that I'm sending to invitations collection are
Invitation(
endAt: DateTime.now().add(Duration(days: 1)),
id: invitationId,
caseId: caseId,
);
Edit
When I try replace newData.child('caseId').val() with caseId value as static string value , it's working !!
For whom faced this issue , I was wrong due to I used newDate in parent not child that I will write so i put my rules under "$invitationId" and its' working.
//only Owner of case can add invitations
"invitations":{
"$invitationId":{
".write" :"root.child('cases').child(newData.child('caseId').val()).child('owner').child(auth.uid).exists()",
}
},

How to configure Firebase relatime database rules?

We receive Firebase warning emails about our realtime database having insecure rules. Which is true, but I am struggling with how to change them. Our application is about 2 apps with a Chat functionality between a passenger and a driver.
This is what we store in the database:
Currently we have:
{
"rules": {
".read": true,
".write": true
}
}
Access should be granted if driverId / passengerId are supplied in the database read or write request.
Help is much appreciated.
I have only basic info about Firebase rules. I hope my below rules can help or atleast give you some idea about how to approach your situation.
So basically for read and write requests, we'll check if your data has the field driverId of passengerId or not:
{
"rules": {
"chats": {
".read": data.hasChildren([‘driverId’, ‘passengerId’]) === "true"
".write": newData.hasChildren([‘driverId’, ‘passengerId’]) === "true"
}
}
}
Here, newData corresponds to the incoming data and data corresponds to the data already and curently residing in the database.
More help can be found here and here in Firebase's official documentation for security rules.
Your rules allow anyone to read & write anything anywhere. You should use authentication (to be able to identify users by their UID in the rules), structure you db so that information with different access logic is stored in different paths, then write rules to enforce your access logic.

How to prevent attacks on firebase functions?

I am seeing some very unusual traffic in my cloud functions and I can only attribute them to an attack (note that I am using the free Spark plan).
The cloud function that gets spammed is:
exports.onPlayerSolvedPuzzle = functions.database.ref('/U/{userId}/CP/{puzzleId}/S')
.onCreate((snapshot, context) =>
{
...
As you can see it is designed to be called when a new data is added in my DB at the path /U/{userId}/CP/{puzzleId}/S
My DB rules specify that only an authenticated user can write data with his ID, additionally I check that this data entry doesn't exist already
{
"rules":
{
"U":
{
"$user_id":
{
".read" : false,
".write": "auth.uid != null && $user_id === auth.uid",
"CP":
{
"$puzzle_id":
{
"S":
{
".validate": "!data.exists()"
},
...
Finally, I have only enabled the "Play Games" and "Game Center" authentications so no anonymous account or email/password account can be used to modify my DB.
When the attacks happen the number of maximum simultaneous connections to my DB jumps from the average of 20 to 100, and the number of cloud function calls jumps from 1000 or less per day to 20000 or more per day eating up my monthly usage quota quite a bit.
What should I do to protect the cloud function calls that I have not already done?
P.S.: when 'onPlayerSolvedPuzzle' executes it writes to another part of the DB to increment the number of people who have solved the puzzle with this 'puzzle_id' (or create an entry if there was none). I have code in place to see if there are entries in that part of the DB that are related to 'puzzle_id' that should not exist (I know the puzzle_id of all the puzzles). When I run this code I don't see any problem at all, so I believe the function is not called with random puzzle_id in the path.
A few things to know:
There's no way to stop someone from invoking one of your public functions. All you can do is write code inside the function to determine if the request is valid and should be acted on. You might want to consider sending an user ID token with the request and verify it with the Admin SDK.
Security rules don't apply to backend code that uses the Admin SDK.
If you're observing what appears to be abusive traffic, you should reach out to Firebase support directly to report it.

Firebase Auth Database permissions by domain [duplicate]

I have a small, personal Firebase webapp that uses Firebase Database. I want to secure (lock down) this app to any user from a single, specific domain. I want to authenticate with Google. I'm not clear how to configure the rules to say "only users from a single, specific domain (say #foobar.com) can read and write to this database".
(Part of the issue that I see: it's hard to bootstrap a Database with enough info to make this use case work. I need to know the user's email at the time of authentication, but auth object doesn't contain email. It seems to be a chicken-egg problem, because I need to write Firebase rules that refer to data in the Database, but that data doesn't exist yet because my user can't write to the database.)
If auth had email, then I could write the rules easily.
Thanks in advance!
If you're using the new Firebase this is now possible, since the email is available in the security rules.
In the security rules you can access both the email address and whether it is verified, which makes some great use-cases possible. With these rules for example only an authenticated, verified gmail user can write their profile:
{
"rules": {
".read": "auth != null",
"gmailUsers": {
"$uid": {
".write": "auth.token.email_verified == true &&
auth.token.email.matches(/.*#gmail.com$/)"
}
}
}
}
You can enter these rules in the Firebase Database console of your project.
Here is code working fine with my database , I have set rule that only my company emails can read and write data of my firebase database .
{
"rules": {
".read": "auth.token.email.matches(/.*#yourcompany.com$/)",
".write": "auth.token.email.matches(/.*#yourcompany.com$/)"
}
}
Code which is working for me.
export class AuthenticationService {
user: Observable<firebase.User>;
constructor(public afAuth: AngularFireAuth) {
this.user = afAuth.authState;
}
login(){
var provider = new firebase.auth.GoogleAuthProvider();
provider.setCustomParameters({'hd': '<your domain>'});
this.afAuth.auth.signInWithPopup(provider)
.then(response => {
let token = response.credential.accessToken;
//Your code. Token is now available.
})
}
}
WARNING: do not trust this answer. Just here for discussion.
tldr: I don't think it's possible, without running your own server.
Here's my attempt thus far:
{
"rules": {
".read": "auth.provider === 'google' && root.child('users').child(auth.uid).child('email').val().endsWith('#foobar.com')",
".write": "auth.provider === 'google' && root.child('users').child(auth.uid).child('email').val().endsWith('#foobar.com')",
"users": {
"$user_id": {
".write": "auth.provider === 'google' && $user_id === auth.uid && newData.child('email').val().endsWith('#foobar.com')"
}
}
}
}
I believe the above says "only allow people to create a new user if they are authenticated by Google, are trying to write into the database node for themselve ($user_id === auth.uid) and their email ends in foobar.com".
However, a problem was pointed out: any web client can easily change their email (using the dev console) before the message is sent to Firebase. So we can't trust the user entry's data when stored into Firebase.
I think the only thing we can actually trust is the auth object in the rules. That auth object is populated by Firebase's backend. And, unfortunately, the auth object does not include the email address.
For the record, I am inserting my user this way:
function authDataCallback(authData) {
if (authData) {
console.log("User " + authData.uid + " is logged in with " + authData.provider + " and has displayName " + authData.google.displayName);
// save the user's profile into the database so we can list users,
// use them in Security and Firebase Rules, and show profiles
ref.child("users").child(authData.uid).set({
provider: authData.provider,
name: getName(authData),
email: authData.google.email
});
As you might be able to imagine, a determined user could overwrite the value of email here (by using the DevTools, for examples).
This should work for anyone looking for a Cloud Firestore option, inspired by Frank van Puffelen's answer.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
// Allows all users to access data if they're signed into the app with an email of the domain "company.com"
allow read, write: if request.auth.uid != null && request.auth.token.email.matches(".*#company.com$");
}
}
}
For anyone really not wanting to have unverified accounts logging in. Maybe dirty, but very effective.
This is my workaround (Angular app):
this.userService.login(this.email.value, this.password.value).then(data => {
if (data.user.emailVerified === true) {
//user is allowed
} else {
//user not allowed, log them out immediatly
this.userService.logout();
}
}).catch(error => console.log(error));

Resources