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