11

What I am trying to achieve is very simple. I need to create a user entry in the database only if it doesn't exist.

The app flow:

  1. Create a user with Firebase Auth (obtain UID)
  2. Create a user document in the database using UID as the key

The client code (create operation):

this.db.doc('users/' + uid).set({username: Santa})

The firestore rules:

service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{uid} {
      allow create: if 
        request.auth != null && 
        request.auth.uid == uid && 
        !exists(/databases/$(database)/documents/users/$(uid));
      allow update: if request.auth != null && request.auth.uid == uid; 
    }
  }
} 

Additional info:

As you may already know the code doesn't work. Everytime I run the client-side code the current user is completely replaced with a new document. However, if I delete the following line from the rules everything works as expected:

allow update: if request.auth != null && request.auth.uid == uid;

But now the question arises, how do I secure data inside user document from unauthorised modifications? Any advice?

benomatis
  • 5,536
  • 7
  • 36
  • 59
manidos
  • 3,244
  • 4
  • 29
  • 65
  • 1
    I would consider using firebase functions for this - instead of the client modifying the users collection. There is an "auth" trigger, that lets you do stuf when a new user is created. – DauleDK Oct 26 '17 at 09:09

6 Answers6

12

If you want to allow creating a document only if it doesn't already exist, just use the allow create rule that you already have. Because you also have an allow update rule, updating the existing data is also allowed.

The following rules should be sufficient:

service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{uid} {
      allow create: if request.auth != null && request.auth.uid == uid;
    }
  }
} 

You don't need the exists() call, because allow create only applies if the data does not exist.

Now to your question: You should clarify what you mean exactly. Now only the authenticated user can modify its own record. If you don't want to allow arbitrary data to be written, check for it.

Here are some examples: https://firebase.google.com/docs/firestore/security/rules-conditions#authentication

mattarau
  • 2,484
  • 1
  • 16
  • 20
crazypeter
  • 1,279
  • 1
  • 10
  • 14
2

Use logic in your code, but not in rules. Add addOnCompleteListener to user collection and after get the result do some actions.

getUsers.document(userUID).get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
@Override
public void onComplete(@NonNull Task<DocumentSnapshot> doc) {
     if(!doc.getResult().exists()){
        //add new user
    }
}                       

}

Rules:

match /UsersProfile/{document=**} {
  allow read, write: if request.auth != null;
}
Ivan Vovk
  • 929
  • 14
  • 28
2

You can now accomplish this with the create method:

create(data) → {Promise.<WriteResult>}

Create a document with the provided object values. This will fail the write if a document exists at its location.

DocumentReference.create

It can also be done even more nicely with Transactions:

create(documentRef, data) → {Transaction}

Create the document referred to by the provided DocumentReference. The operation will fail the transaction if a document exists at the specified location.

Transaction.create

forresthopkinsa
  • 1,339
  • 1
  • 24
  • 29
0

I had use this and it worked. Basically I make sure the user is logged in by request.auth != null and then I check to see if the resource requested is null. If the resource already exists, then it means the user exists.

I added in the allow update in case you wanted only the user to change their own data.

match /users/{document} {
      allow create: if request.auth != null && resource == null;
      allow update: if request.auth != null && request.auth.uid == resource.data.author_uid;
}
Jay Patel
  • 223
  • 1
  • 3
  • 6
0

Below code creates document only if not existing else throws an exception

DocumentReference reference = db.collection("ordersCollection").document(userId);
    try {
        ApiFuture<WriteResult> future = reference.create(userPojo);
        System.out.println("Update time : " + future.get().getUpdateTime());
        System.out.println("created entity "+userPojo.getUserId());
    } catch (InterruptedException | ExecutionException e) {
        throw new RuntimeException(e);
    }
suraj bahl
  • 2,864
  • 6
  • 31
  • 42
-1

this.db.doc('users/' + uid).set({username: Santa}, {merge: true}) It should merge and not replace. Fields omitted will remain untouched. https://firebase.google.com/docs/reference/js/firebase.firestore.DocumentReference#set