Prashin Jeevaganth
Prashin Jeevaganth

Reputation: 1343

Firebase Permission denied Error on Firebase emulator

I am referencing this tutorial for Firestore security rules. I have extracted the code from the repository and it matches that of the video. I changed the setup code to run the firestore.rules instead of firestore-test.rules, and tried running firebase emulators:start and jest ./spec following the same directory structure, I fail the tests of "should allow delete when user is admin" and "should not allow delete for normal user" and the reason it is failing is due to the write rule in the wildcard. Does anyone know what is wrong?

collections.spec.js

const { setup, teardown } = require("./helpers");


describe("General Safety Rules", () => {
  afterEach(async () => {
    await teardown();
  });

  test("should deny a read to the posts collection", async () => {
    const db = await setup();
    const postsRef = db.collection("posts");
    await expect(postsRef.get()).toDeny();
  });

  test("should deny a write to users even when logged in", async () => {
    const db = await setup({
      uid: "danefilled"
    });

    const usersRef = db.collection("users");
    await expect(usersRef.add({ data: "something" })).toDeny();
  });
});

describe("Posts Rules", () => {
  afterEach(async () => {
    await teardown();
  });

  test("should allow update when user owns post", async () => {
    const mockData = {
      "posts/id1": {
        userId: "danefilled"
      },
      "posts/id2": {
        userId: "not_filledstacks"
      }
    };

    const mockUser = {
      uid: "danefilled"
    };

    const db = await setup(mockUser, mockData);

    const postsRef = db.collection("posts");

    await expect(
      postsRef.doc("id1").update({ updated: "new_value" })
    ).toAllow();

    await expect(postsRef.doc("id2").update({ updated: "new_value" })).toDeny();
  });

  test("should allow delete when user owns post", async () => {
    const mockData = {
      "posts/id1": {
        userId: "danefilled"
      },
      "posts/id2": {
        userId: "not_filledstacks"
      }
    };

    const mockUser = {
      uid: "danefilled"
    };

    const db = await setup(mockUser, mockData);

    const postsRef = db.collection("posts");

    await expect(postsRef.doc("id1").delete()).toAllow();

    await expect(postsRef.doc("id2").delete()).toDeny();
  });

  test("should allow delete when user is admin", async () => {
    const mockData = {
      "users/filledstacks": {
        userRole: "Admin"
      },
      "posts/id1": {
        userId: "not_matching1"
      },
      "posts/id2": {
        userId: "not_matching2"
      }
    };

    const mockUser = {
      uid: "filledstacks"
    };

    const db = await setup(mockUser, mockData);

    const postsRef = db.collection("posts");

    await expect(postsRef.doc("id1").delete()).toAllow();
  });

  test("should not allow delete for normal user", async () => {
    const mockData = {
      "users/filledstacks": {
        userRole: "User"
      },
      "posts/id1": {
        userId: "not_matching1"
      },
      "posts/id2": {
        userId: "not_matching2"
      }
    };

    const mockUser = {
      uid: "filledstacks"
    };

    const db = await setup(mockUser, mockData);

    const postsRef = db.collection("posts");

    await expect(postsRef.doc("id1").delete()).toDeny();
  });

  test("should allow adding a post when logged in", async () => {
    const db = await setup({
      uid: "userId"
    });

    const postsRef = db.collection("posts");
    await expect(postsRef.add({ title: "new_post" })).toAllow();
  });

  test("should deny adding a post when not logged in", async () => {
    const db = await setup();
    const postsRef = db.collection("posts");
    await expect(postsRef.add({ title: "new post" })).toDeny();
  });
});

firestore.rules

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {



    // lock down the db
    match /{document=**} {
      allow read: if false;
      allow write: if false;
    }

    match /posts/{postId} {
      allow update: if userOwnsPost();
      allow delete: if userOwnsPost() || userIsAdmin();
      allow create: if loggedIn();
    }

    function loggedIn() {
      return request.auth.uid != null;
    }

    function userIsAdmin() {
      return getUserData().userRole == 'Admin';
    }

    function getUserData() {
      return get(/databases/$(database)/documents/users/$(request.auth.uid)).data
    }

    function userOwnsPost() {
      return resource.data.userId == request.auth.uid;
    }
  }
}

Error trace from terminal

FirebaseError: 7 PERMISSION_DENIED: 
false for 'create' @ L10


  ● Posts Rules › should not allow delete for normal user

FirebaseError: 7 PERMISSION_DENIED: 
false for 'create' @ L10

  at new FirestoreError (/Users/../../../../../../../../../Resources/rules/node_modules/@firebase/firestore/src/util/error.ts:166:5)
  at ClientDuplexStream.<anonymous> (/Users/../../../../../../../../../Resources/rules/node_modules/@firebase/firestore/src/platform_node/grpc_connection.ts:240:13)
  at ClientDuplexStream._emitStatusIfDone (/Users/../../../../../../../../../Resources/rules/node_modules/grpc/src/client.js:234:12)
  at ClientDuplexStream._receiveStatus (/Users/../../../../../../../../../Resources/rules/node_modules/grpc/src/client.js:211:8)
  at Object.onReceiveStatus (/Users/../../../../../../../../../Resources/rules/node_modules/grpc/src/client_interceptors.js:1311:15)
  at InterceptingListener._callNext (/Users/../../../../../../../../../Resources/rules/node_modules/grpc/src/client_interceptors.js:568:42)
  at InterceptingListener.onReceiveStatus (/Users/../../../../../../../../../Resources/rules/node_modules/grpc/src/client_interceptors.js:618:8)
  at /Users/../../../../../../../../../Resources/rules/node_modules/grpc/src/client_interceptors.js:1127:18

Upvotes: 5

Views: 6678

Answers (2)

shteeven
shteeven

Reputation: 532

For those that are currently having this issue firestore 8.6.1 (or equivalent), there is a bug discussed here: https://github.com/firebase/firebase-tools/issues/3258#issuecomment-814402977

The fix is to downgrade to firestore 8.3.1, or if you are reading this in the future and firestore >= 9.9.0 has been released, upgrade to that version.

Upvotes: 1

Marcowindt
Marcowindt

Reputation: 91

I actually followed the same tutorial to get started with the firebase emulator and got the same kind of error messages. The problem for me was that when you start the simulator it automatically looks for your firestore.rules file and loads the rules. So, when you then add your mockData the rules already apply.

In order to make your test code work either change the setting for your firestore rules file in your firebase.json to a non-existing file (or rules file that allows all read/write) or add the mockData as an admin in your setup function, e.g.:

module.exports.setup = async (auth, data) => {
  const projectId = `rules-spec-${Date.now()}`;
  const app = firebase.initializeTestApp({
    projectId,
    auth
  });

  const db = app.firestore();

  // Initialize admin app
  const adminApp = firebase.initializeAdminApp({
    projectId
  });

  const adminDB = adminApp.firestore();

  // Write mock documents before rules using adminApp
  if (data) {
    for (const key in data) {
      const ref = adminDB.doc(key);
      await ref.set(data[key]);
    }
  }

  // Apply rules
  await firebase.loadFirestoreRules({
    projectId,
    rules: fs.readFileSync('firestore.rules', 'utf8')
  });

  return db;
};

Hope this helps.

Also see this question

Upvotes: 9

Related Questions