Why do my Firebase Realtime database rules break my HandleValueChanged? - firebase

Good morning!
I am working on a Firebase project in my Unity game and am working on securing the data in the database, however whenever I update the rules, it breaks my (otherwise working) handlevaluechanged function.
The data structure looks like this:
User_List ->
Firebase_User_ID ->
Health: 100,
Name: Foo,
etc ....
I want the rules (at a minimum, I'll add validation later) for this data to be:
"USER_LIST":
{
"$UID" :
{
".read": "auth.uid === $UID",
".write" : "auth.uid === $UID",
}
},
Locally in the game, I get a reference with this call:
FirebaseDatabase.DefaultInstance.GetReference("USER_LIST").Child(USER_ID).ValueChanged += HandleValueChanged;
If I set the rules to:
".read": true,
".write" : true
everything works as expected. The client can update the database, and handlevaluechanged does its job of keeping the local data synced. When I switch the to the rules above where I verify the auth ID, the client still works. It can update the database no issues, provided the correct user ID is signed in. However, my Handlevaluechanged gets a permission denied error from firebase, as if the handlevaluechanged listener does not supply the proper user ID when it attempts a read from the DB.
I'm puzzeled because the rules allow me to get the reference in the first place, and update the database from the client, but I can't update the client from the database? What am I missing?
I have also tried to GetReference at the USER_LIST node instead of the USER_ID node with the same result.
Any insight would be greatly appreciated! Thank you in advance.

After ignoring this issue for a while and working on other bugs, I discovered the true issue and wanted to post in case anyone else finds themselves in this pickle.
I had a Unity scene dedicated to signing in users that created the authenticated Firebase user, then jumped to a new scene. In that new scene, I searched for the local user object created by when the user logged in, and then I set up the database reference and the handleValueChanged listener.
I think the issue was that searching for the user was being done asynchronously (I have not done more research to confirm this, but my testing in this specific case seems to suggest this is the case), so the task was started, and while it was looking for the user credentials, it created the DB reference and added a listener with a null user. Afterwards, the task completes and the user is no longer null, but the listener is already listening.
Then, if I did a database read or write, the call for read or write was from an authenticated user and would execute provided the RTB rules were satisfied. Successful writes would trigger the handlevaluechanged listener and draw up a permission denied error due to the listener not having an authenticated user ID associated with it (again, I reached this conclusion based on observation and testing with this specific project, not research).
Simply adding the DB reference to my local user object after it was created instead of searching for the user and then establishing the reference solved all of my issues.
I hope this helps!

Related

Calling .setPersistenceEnabled(false) when logging out of app, not working

In my Flutter/Dart mobile app I make use of Firebase RTDB persistence to enable offline use of the app.
My understanding is that to enable persistence you have to make the call, as per the following piece of code, before using any database references to eg. query the database. I use the following piece of code to enable persistence immediately after loading the app and it works fine:
FirebaseDatabase firebaseDatabase = FirebaseDatabase.instance;
bool _success = await firebaseDatabase.setPersistenceEnabled(true);
print(_success); // Prints true, so persistence is set 'on'.
When I logout of the app I attempt to turn persistence off with:
bool _success = await firebaseDatabase.setPersistenceEnabled(false);
print(_success); // Prints false, so persistence is still 'on', ie. the call failed.
I assume the reason persistence cannot be turned off is because there have been calls to db references prior to trying to switch it off.
This leads to three questions, I guess:
Should I be worried about turning it off at all, when I logout? The reason I attempt it is good house-keeping, mainly. I clean up shared preferences, close keepsyncd's, etc when logout is run. Also, though, the user can have multiple userids to login and I want to make sure that I am not retaining persisted data from their previous login id.
Related to 1, does setting persistence to false clear the cache of
data and potential queued calls to the db?
If the answers to 1 and 2 are 'yes', how can I switch persistence off given the code I'm using to do so keeps telling me it failed?
The typical way to handle this is to enable persistence once a user logs in.
Once disk persistence has been enabled and your app has used the database, it cannot be turned off. The documentation says this about it:
The returned Future will complete with true if the operation was successful or false if the persistence could not be set (because database references have already been created).
That last bit is clearly the case for you: you've been using the database already, which means that disk persistence is on.
To your specific questions:
Unfortunately the data in the local cache cannot be cleared up through the API at the moment. It is a valid feature request, but for now you'll have to assume that any data on the device can be seen by any user on that device (or device profile).
Disabling disk persistence keep the client from adding data to the cache. It does not clear any existing data in the cache.

Is there a way to increment a count in Firebase without exposing current count to the client?

I would like to increment a count in Firebase, but without exposing the the current count to client.
I have read numerous helpful answers about using transactions to increment counters in Firebase, but all of these assume that the client has "read" access to the counter element. What if the client shouldn't have read access to the count, but I want to increment the value based on the user's actions?
Example:
Suppose I want to track views or impressions (or some other user action) on a specific piece of content. The counts are for internal eyes only, and perhaps to be shared privately with advertisers and partners -- but not shown to the users themselves, and we don't want to share that data with competitors by exposing it to the browser.
For cases like this, I could route things through a server process, in which the server process fetches the count and increments it, but that kinda defeats the purpose of using Firebase in the first place. Are there any other options to increment a "private" counter from the browser?
I'm not sure why you're getting downvoted, as it seems like a valid question to me.
There is no server-side way to increment a counter in Firebase. With that knowledge, the only way to currently increment a counter is to have the client perform the increment. While you can secure that with rules to ensure that each user can only increment once, that each increment is a single step and many other things, the client-app will have to be able to read the current value.
The common solution to your problem would be to introduce a tally server. Each client can write it's "I've viewed this content" record into the database and then you have a server that listens to this queue and updates the count.
viewQueue
$pushId
uid: <uid>
itemId: <itemId>
viewCounts
$itemId: <totalViewCount>
You could secure this so that users can only write to the view queue and only the server (which likely will run under a service account can access the view counts:
{
"rules": {
"viewQueue": {
".read": false, // only the server can read this
"$pushid": {
".write": "newData.exists() && !data.exists()"
}
},
"viewCounts": {
// only the server can access this
".read": false,
".write": false
}
}
}

How do I use Firebase to handle automatic server-side calculations?

Perhaps my question should be restated as: how do I refactor those behaviours into CRUD, which is what Firebase excels at?
I get that CRUD works well. I also see how the Firebase declarative security model allows me to ensure proper security server-side, where it should exist.
Let's say I have a subscription service. Each time a person signs up for a service, they need to automatically have a "due" line item added to their account. In simple terms:
/users/john
/services/goodstuff
So john can sign up for goodstuff, I might let him in for 30 days without paying, but will remind him when 30 days is up, "hey, you need to pay or else you lose your subscription to goodstuff."
With a server back-end, I would POST to /services/goodstuff/members, e.g., have part of the POST handler add a "you owe" line item to john's account, ensuring that no one can join goodstuff without being marked as owing.
In a Firebase BaaS app, where those server-side logics don't exist, how would I refactor the app to get the same effective behaviour?
Update (March 10, 2017): While the architecture I outline below is still valid and can be used to combine Firebase with any existing infrastructure, Firebase just released Cloud Functions for Firebase, which allows you to run JavaScript functions on Google's servers in response to Firebase events (such as database changes, users signing in and much more).
One potential solution (untested, sorry; but it should be the right idea):
{
"rules": {
"users": {
"$user": {
/* When they create their user record, they must write a 'due' that's
* within the next 30 days. */
".write": "!data.exists() && newData.child('due').isNumber() && newData.child('due').val() < now + (30*24*60*60*1000)"
}
},
"services":
"$service": {
/* Must be authenticated and due date must not be passed. */
".read": "auth != null && now < root.child('users/' + auth.id + '/due).val()"
}
}
}
}
This would require that when somebody logs in for the first time and initializes their users/ entry, they'd have to write a due date in the next 30 days. And then when accessing any service, that due date would be verified to have not passed.
That said, another option is to just spin up a tiny backend service to handle this sort of business logic. Firebase excels at protecting, storing, and synchronizing data. But if you have complicated business logic, you might want to think about spinning up a tiny backend process. Firebase has a REST api as well as Node.JS and JVM clients, so it's really easy to run your own backend code that integrates with Firebase.

Possibility for only currently connected (not authenticated) and admin user to read and write on certain location

Is there any way to write a security rule or is there any other approach that would make possible only for currently connected (not authenticated) user to write/read certain location - admin should also be able to write/read?
Can a rule be written that disallows users to read of complete list of entries and let them read only entry that matches some identifier that was passed from client?
I'm trying to exchange some data between user and Node.js application through Firebase and that data shouldn't be able to read or write by anyone else other than user and/or admin.
I know that one solution would be that user requests auth token on my server and uses it to authenticate on Firebase and that would make it possible to write rule which prevents reads and writes. However, I'm trying to avoid user connecting to my server so this solution is not first option.
This is in a way session based scenario which is not available in Firebase but I have
some ideas that could solve this kind of problem - if implemented before session management:
maybe letting admin write into /.info/ location which is observed by client for every change and can be read only by active connection - if I understood correctly how .info works
maybe creating .temp location for that purpose
maybe letting admin and connected client could have more access to connection information which would contain some connection unique id, that can be used to create location with that name and use it inside rule to prevent reading and listing to other users
Thanks
This seems like a classic XY problem (i.e. trying to solve the attempted solution instead of the actual problem).
If I understand your constraints correctly, the underlying issue is that you do not wish to have direct connections to your server. This is currently the model we're using with Firebase and I can think of two simple patterns to accomplish this.
1) Store the data in an non-guessable path
Create a UUID or GID or, assuming we're not talking bank level security here, just a plain Firebase ID ( firebaseRef.push().name() ). Then have the server and client communicate via this path.
This avoids the need for security rules since the URLs are unguessable, or close enough to it, in the case of the Firebase ID, for normal uses.
Client example:
var fb = new Firebase(MY_INSTANCE_URL+'/connect');
var uniquePath = fb.push();
var myId = uniquePath.name();
// send a message to the server
uniquePath.push('hello world');
From the server, simply monitor connect, each one that connects is a new client:
var fb = new Firebase(MY_INSTANCE_URL+'/connect');
fb.on('child_added', newClientConnected);
function newClientConnected(snapshot) {
snapshot.ref().on('child_added', function(ss) {
// when the client sends me a message, log it and then return "goodbye"
console.log('new message', ss.val());
ss.ref().set('goodbye');
});
};
In your security rules:
{
"rules": {
// read/write are false by default
"connect": {
// contents cannot be listed, no way to find out ids other than guessing
"$client": {
".read": true,
".write": true
}
}
}
}
2) Use Firebase authentication
Instead of expending so much effort to avoid authentication, just use a third party service, like Firebase's built-in auth, or Singly (which supports Firebase). This is the best of both worlds, and the model I use for most cases.
Your client can authenticate directly with one of these services, never touching your server, and then authenticate to Firebase with the token, allowing security rules to take effect.

Unable to delete information from my firebase

I was testing some changes to my firebase and accidently submitted 100's of updates to a reference. Now when I try and delete the reference it then creates a new reference with different data.
I have tried deleting everything in the firebase but it will just keep creating a new reference.
In this specific example I used set() to add 5 random values to a user name Michael. The 5 random values were called 100's of times and now when I delete the Michael user to test again it already has a value queued up and recreates itself immediately. I looked at my upload usage and it showed a huge amount of data being uploaded at one point that coincides with this error.
Any idea how to remove these queued up changes?
Make sure to disconnect the client that is writing this data. I suspect somewhere you have a process running that is generating these writes.
If you can't stop the offending process for some reason, you could always modify your security rules to deny access to the client that's doing the writes (or if it's a server using a Firebase Secret to authenticate, you could revoke its secret).
I've had a similar issue - think it has to do with your session / caching.
Try logging out of firebase and back in - if the records are still there, make a backup of your security rules, then use:
{
"rules": {
".read": false,
".write": false
}
}
and delete them.

Resources