2

I'm building an app where users can rotate a wheel of fortune. When they are lucky and get a specific field, they can win a coupon.

I wanted to store those coupons in firebase Firestore with following properties:

{
"code" : 381939,
"expire_date" : xxx,
"value" : 5,
"currency" : "EUR"
}

My thoughts were that when somebody rotates the wheel of fortune, a random coupon/document will be chosen from the database. If there aren't any coupons, the wheel of fortune should be inactive. So I think I need to place a button "check availability for coupons" or similar to check if there are any coupons left. When there are any, the wheel will be set to active.

Then there are two scenarios: 1) they win: The coupon will be assigned to the user and deleted from the database 2) they loose: The coupon will be stored back in the database.

Unfortunately I don't think that this will work pretty well, when lots of user want to use the wheel of fortune simultaneously because multiple users could read the same document/coupon.

So is it possible to "lock" a document, so nobody else can read it, after somebody reads it? Or how can I make sure that every user will read a different coupon/document from the database, so there aren't users sharing this coupon?

Hope you understand my question.

If you need any more information, just tell me.

Tobi
  • 133
  • 1
  • 5
  • If you don't use a backend service such as Cloud Functions to manage strict control of these documents, you're opening up yourself to abuse from anyone who could simply rapidly claim all the documents by accessing the database directly, bypassing your app's logic. Also note that, without a backend, you can't "lock" a document simply by reading it. You can't arrange for anything like this to happen on a simple read operation. – Doug Stevenson Mar 30 '19 at 20:03

2 Answers2

2

You can restrict access to a file with Cloud Firestore's server-side security rules. Since you're talking about associating the coupon/document with a user, that is a great way to also model your security rules.

For example, say you add a owner field to the document with the user's UID, if they've claimed the coupon:

{
  "code" : 381939,
  "expire_date" : xxx,
  "value" : 5,
  "currency" : "EUR"
  "owner" : "uidOfOwner"
}

Now we'll ensure that:

  1. Users can only read a document if it doesn't have an owner, or if they're the owner.
  2. Users can only write to a document if it doesn't have an owner yet.

We can do that with these rules:

match /databases/{database}/documents {
  match /coupons/{coupon} {
    allow read: if !resource.data.keys().hasAll(['owner']) || resource.data.owner == request.auth.uid;
    allow write: if !resource.data.keys().hasAll(['owner']);
  }
}

A few things to note here:

  • With the above rules you control access to individual documents, but users won't be able to read/query the entire coupons collection anymore. If you still want to allow that, have a look at securing queries.
  • With the above rules, the user can modify anything they want about a document without an owner (or of which they are the owner). You'll probably want to limit the write permission further, so that the user can only write the owner field. See this answer on checking if a field is modified.
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • thank you very much for your fast answer! Because this is my first app with Firestore I had all rules to "everybody can read and write anything" because it's just in development. I wanted to change those when I'm nearly finished so I didn't thought of this. Thank you very much! – Tobi Mar 30 '19 at 20:16
  • The caveat here is that someone could easily query for and claim all the documents without any restriction. I imagine you would need a backend to prevent that from happening. – Doug Stevenson Mar 30 '19 at 20:28
0

I think you should use a firebase cloud function for security concerns because you users should not be able to write on coupons. When a user win, call a cloud function that will randomly add an ownerId to an available coupon :

{
  "code" : 381939,
  "expire_date" : xxx,
  "value" : 5,
  "currency" : "EUR"
  "owner" : "ownerId"
}

On client side, you can listen to a query that will get coupons associated to the user. By doing so, when a user win, he will be notified through your listener.

Do not forget to add security rules so your users can only get the coupons they are associated to :

 match /databases/{database}/documents {
      function authenticated() { return request.auth.uid != null }
      function isMyCoupons () { return request.auth.uid == resource.data.owner }

      match /coupons/{coupon} {
        allow read: authenticated() && isMyCoupons();
      }
    }