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

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.

Related

Why do my Firebase Realtime database rules break my HandleValueChanged?

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!

Adding a token to Firebase Storage for group security

For our app we need to be able to offer groups access to files. Each user can have a large number of groups so it does not make sense to use the "custom token" solution (which is anyways very awkward.
As I discovered, Firebase is very limited with its Storage security rules. The main issue is that we keep Group definitions in Firestore which the Storage security rules do not have access to.
To overcome this we decided to include in the metadata of each uploaded file a "token" which anyone in the group has access to. When they download a file, they need to provide this token in the request params (e.g. /groups/xxx/filename.jpg?token=abc).
So I went ahead and wrote these rules:
match /groups/{groupId}/{filename} {
allow read: if request.auth != null && request.params.token == resource.metadata.token;
allow write: if request.auth.uid == userId
&& request.resource.size < 1 * 1024 * 1024
&& request.resource.contentType.matches('image/.*')
&& (resource == null || request.resource.contentType == resource.contentType)
&& imageId.size() < 32
;
}
But when I run it in the simulator I get the error: "Error: simulator.rules line [23], column [43]. Property params is undefined on object." which points to the rule with "request.params.token"
The documentation specifically states that we have access to the params object from the request object: https://firebase.google.com/docs/storage/security/secure-files?authuser=0#request_evaluation
Due to the severe limitations of Firebase being able to query Firestore data in Storage and the incorrect documentation regarding request.param not being available, we had to use a different approach.
We decided to use the following URL when querying files of a group:
/groups/{groupId}/{token}/{filename}
The only security requirement is for the user to be logged in. Since the above token is a secret and only available to group members, we find it to be pretty secure. We do not allow listing of directories so it is not possible to simply list the /groups/{groupId} to find any tokens.
Note: another optimization could be to not include the {groupId} in the path but we felt it was better to include for debug and management purposes.
If anyone feels this is an insecure way, please let us know on the comments!
Thanks!
It sounds like you're trying to proxy some data from Cloud Firestore into a Cloud Storage request for use in security rules. This is currently not feasible. (On top of that request.params currently refers to APIs that aren't documented, so it can't really be used. I've brought this up with the team, and we feel request.params should likely be removed from security rules docs.)
If you want to assign a Firebase Auth user some group identity that can be verified across products (Firestore, Storage, and Realtime Database), you can use custom claims for this. It will require some work on your backend to assign, directly to the user account, the value(s) you want to check. If you do it correctly, the values you set in that JSON blob will show up in request.auth.token in both Firestore and Storage security rules.

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
}
}
}

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