Raul
Raul

Reputation: 3081

Firebase Security Rule - Access a field in other document

Introduction

I have this structure on my db

C- usernames
   D- paola
        -> userId: 7384-aaL732-8923dsnio92202-peesK
   D- alex
        -> userId: ...
   D- adam
        -> userId: ...


 C- users
    D- userId of paola
        -> username: "paola"
        -> ...
    D- userId of alex
        -> username: "alex"
        -> ...
    D- userId of adam
        -> username: "adam"
        -> ...

I am signing up users in the client side so I have had to write some security rules...

In my client code I do:

  1. Add the username (document id) with the userId (document data) to the usernames collection
  2. Create a user document in the users collection with the username and other stuff.

Security Rules

So, my security rules look like this:

function isUsernameOwner(username) {
    return get(/databases/$(database)/documents/usernames/$(username)).data.userId == request.auth.uid;
}

match /users/{userId} {
   // Every people can read the users collection (might be in the sign in form)
   allow read: if true;
   
   // As the users creation is made in the client side, we have to make sure
   // it meets these requirements
   allow write: if isSignedIn() &&
        isSameUser(userId) &&
      request.resource.data.keys().hasOnly(['email', 'username', 'name', 'birthday']) &&
      isValidUsername(request.resource.data.username) &&
      isUsernameOwner(request.resource.data.username); // <------- If I remove this all works fine
}

Problem

When I try to sign up, I get "Missing or insufficent permissions"... I think the problem is in the function isUsernameOwner() but I don't know what am I doing wrong... Am I accessing incorrectly the field userId in the username document? If not, is it possible that the batched write doesn't happen sequentially?

Pd: The signup process is made using a batched write (first write the username, then the user)

UPDATE

This is the javascript code in which I make the batched write:

 // Firebase.js
 createUser = (email, password, username, name, birthday) => {
    return this.auth
      .createUserWithEmailAndPassword(email, password)
      .then((currentUser) => {
        // Get the user id
        const userId = currentUser.user.uid;

        // Get a new Firestore batched write
        const batch = this.db.batch();

        // Create the new username document in the usernames collection with the user's id as field
        const usernameRef = this.db.collection("usernames").doc(username);
        batch.set(usernameRef, { userId });

        // Create the new user document in the users collection with all the relevant information
        const userRef = this.db.collection("users").doc(userId);
        birthday = firebase.firestore.Timestamp.fromDate(new Date(birthday)); // It is neccessary to convert the birthday to a valid Firebase Timestamp
        const data = {
          email,
          username,
          name,
          birthday,
        };
        batch.set(userRef, data);

        // Commit the batch
        return batch.commit();
      })
      .catch((err) => {
        throw err;
      });

Upvotes: 0

Views: 209

Answers (1)

Victor Molina
Victor Molina

Reputation: 2641

I think the problem is that you are using get() in your security rule global function. Make it local and use getAfter instead to wait until the 'termination' of the batched write.

Here you can see a post which might be useful for your case: Firebase security rules difference between get() and getAfter()

Just see the Doug answer, he explains the differences between get and getAfer.

Upvotes: 1

Related Questions