Skip to content

Commit

Permalink
feat: tighten security rules
Browse files Browse the repository at this point in the history
  • Loading branch information
czabaj committed Aug 9, 2023
1 parent 260cced commit 41526fb
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 7 deletions.
18 changes: 17 additions & 1 deletion firestore.rules
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
match /places/{placeID} {
allow list: if request.auth != null && resource.data.users[request.auth.uid] >= 0;
// anyone can get to allow search from ShareLink
allow get: if request.auth != null;
// anyone can write to allow update from the invitation - TODO: candidate for a cloud function
allow write: if request.auth != null;
match /{subcollection=**} {
allow read, write: if request.auth != null;
}
}
match /shareLinks/{shareLink=**} {
// allow "list" b/c we query for existing link in the upsert logic
allow read, write: if
request.auth != null
}
match /webAuthnUsers/{userID} {
allow get: if request.auth.uid == userID;
allow list: if false;
allow write: if false;
}
}
}
133 changes: 127 additions & 6 deletions firestore.rules.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import { describe, expect, test } from "vitest";

const testUserId = "KQC3YpthFJfKWOrSZhtz5O3Lm302";

describe("Security rules", () => {
describe("Places", () => {
const testPlaceId = "place1";
const testKegId = "keg1";

let testEnv: testing.RulesTestEnvironment;
let authenticatedUser: testing.RulesTestContext;
let unauthenticatedUser: testing.RulesTestContext;
Expand All @@ -24,13 +27,131 @@ describe("Security rules", () => {
unauthenticatedUser = testEnv.unauthenticatedContext();
});

it("should not allow to read for unauthenticated users", async () => {
const readUser = unauthenticatedUser
beforeEach(async () => {
// Setup initial user data
await testEnv.withSecurityRulesDisabled((context) => {
const firestoreWithoutRule = context.firestore();
const placeDoc = firestoreWithoutRule
.collection("places")
.doc(testPlaceId)
.set({
users: {
[testUserId]: 100,
},
});
const placeKeg = firestoreWithoutRule
.collection("places")
.doc(testPlaceId)
.collection("kegs")
.doc(testKegId)
.set({ beer: `Pilsner Urquell` });
return Promise.all([placeDoc, placeKeg]).then(() => {});
});

// Create authenticated and unauthenticated users for testing
authenticatedUser = testEnv.authenticatedContext(testUserId);
unauthenticatedUser = testEnv.unauthenticatedContext();
});

it(`should not allow to read for unauthenticated users`, async () => {
const getDoc = unauthenticatedUser
.firestore()
.collection("places")
.doc("random-id")
.get();

await testing.assertFails(getDoc);
});

it(`should allow list if user has a reference`, async () => {
const listDocs = authenticatedUser
.firestore()
.collection("places")
.where(`users.${testUserId}`, `>=`, 0);
await testing.assertSucceeds(listDocs.get());
});

it(`should allow get if user has a reference`, async () => {
const getDoc = authenticatedUser
.firestore()
.collection("users")
.doc("Test-User")
.collection("places")
.doc(testPlaceId)
.get();

await testing.assertFails(readUser);
await testing.assertSucceeds(getDoc);
});

it(`should allow get place subcollection if user can read place`, async () => {
const getDoc = authenticatedUser
.firestore()
.collection("places")
.doc(testPlaceId)
.collection("kegs")
.where(`beer`, `==`, `Pilsner Urquell`)
.get();

await testing.assertSucceeds(getDoc);
});
});

describe("WebAuthnUsers", () => {
let testEnv: testing.RulesTestEnvironment;
let authenticatedUser: testing.RulesTestContext;
let unauthenticatedUser: testing.RulesTestContext;

beforeAll(async () => {
testEnv = await testing.initializeTestEnvironment({
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
firestore: {
rules: fs.readFileSync("firestore.rules", "utf8"),
host: "127.0.0.1",
port: 9090,
},
});

authenticatedUser = testEnv.authenticatedContext(testUserId);
unauthenticatedUser = testEnv.unauthenticatedContext();
});

beforeEach(async () => {
// Setup initial user data
await testEnv.withSecurityRulesDisabled((context) => {
const firestoreWithoutRule = context.firestore();
return firestoreWithoutRule
.collection("webAuthnUsers")
.doc(testUserId)
.set({ foo: "bar" });
});

// Create authenticated and unauthenticated users for testing
authenticatedUser = testEnv.authenticatedContext(testUserId);
unauthenticatedUser = testEnv.unauthenticatedContext();
});

it(`should not allow to read for unauthenticated user`, async () => {
const getDoc = unauthenticatedUser
.firestore()
.collection("webAuthnUsers")
.doc("random-id")
.get();
await testing.assertFails(getDoc);
});

it(`should not allow to read for a random user document`, async () => {
const getDoc = authenticatedUser
.firestore()
.collection("webAuthnUsers")
.doc("random-id")
.get();
await testing.assertFails(getDoc);
});

it(`should allow to read the user own document`, async () => {
const getDoc = authenticatedUser
.firestore()
.collection("webAuthnUsers")
.doc(testUserId)
.get();
await testing.assertSucceeds(getDoc);
});
});

0 comments on commit 41526fb

Please sign in to comment.