4

I've found that question relatively often asked here, but i still cant figure out how to manage a rule for unique properties. I have following document datamodel:

users/{usereId}/Object
users/usernames/Object

The first Object contains basic information about the user, like:

{
email: "example@hotmail.edu"
photoURL: null
providerId: null
role: "admin"
username:"hello_world"
}

meanwhile the usernames objects only contains the username as the property key and the uid as the value, for instance:

{
hello_world:"F3YAm8ynF1fXaurezxaQTg8RzMB3"
}

I set it up this way, because I want that every user has a unique username. And its less time consuming iterating through the second object than through the first ones. But back to my issue. I need that hello_world is unique within the write operation. But my rules so far does not work. I have:

service cloud.firestore {
  match /databases/{database}/documents {
  
    match /{document=**} {
      allow read, write: if request.auth.uid != null
    }
    
    match /users/{userID} {
        allow create: if !exists(/databases/$(database)/documents/users/$(request.resource.data.username)) <== does not apply
    }
    
  }
}

The second match is, what should apply the unique property rule. Has anyone an idea how to set the rule correctly?

In the console the object model looks as follows

enter image description here

enter image description here

Community
  • 1
  • 1
MarcoLe
  • 2,309
  • 6
  • 36
  • 74
  • Can you show a screen of what `users/usernames/Object` looks like in the console? Right now I find it hard to parse collections and documents in that structure. – Frank van Puffelen Jul 16 '18 at 14:12
  • Of, course. I updated the question. :) – MarcoLe Jul 16 '18 at 14:18
  • OK. So you created a document called `usernames` in your `users` collection, to track the names that are in use. But your rules are trying to find a document named after the current user's name, which will never exist in this structure. – Frank van Puffelen Jul 16 '18 at 15:09

2 Answers2

5

You created a document called usernames in your users collection, to track the names that are in use. But your rules are trying to find a document named after the current user's name, which will never exist in this structure.

In your current structure, you will need something like this:

allow create: if get(/databases/$(database)/documents/users/usernames).data[$(request.resource.data.username)] == request.auth.uid

So the above gets the document where you keep all the user names, and then checks if the current user name is in their for the current UID.

An alternative approach is to keep an additional colllection of all user names, and then make each document in there map a single user names to a UID. In this scenario your /usernames collection would be top-level, since it's shared between all users. The rules for this would be closer to what you currently have:

allow create: if !exists(/databases/$(database)/documents/usernames/$(request.resource.data.username))
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • 2
    Always open for good advices, since i am a beginner of object oriented database structures. Do you mean the `usernames` collection as top-level in addition to the `users`? At the end I would basically have `usernames/{usernameAsUid}/Object` along with `users/{userUid}/Object` – MarcoLe Jul 16 '18 at 17:43
  • 1
    Yeah, having an *additional* top-level `/usernames` collection would be one of the options. – Frank van Puffelen Jul 16 '18 at 18:12
2

Since your usersnames have to be unique, wouldn't it be an option to use their names as the document key?

  • yeah also thought about that. But at the meantime I have made a another datastructure where username does not need to be unique. The email has to. And `firebase` automatically checks for the email to be unique. – MarcoLe Jul 31 '18 at 10:32
  • 3
    You can do it that way, but then changing your users' username is a big hassle – Helder Esteves Jun 25 '20 at 13:09