Skip to content

Commit

Permalink
feat(security rules): implemented Firestore security rules
Browse files Browse the repository at this point in the history
Implemented security rules to protect data in the Firestore db. This includes test cases to verify
the correctness of the security rules.

fix quadratic-funding#28
  • Loading branch information
ctrlc03 authored and 0xjei committed Feb 3, 2023
1 parent 5d34de1 commit 7fb1c4a
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 5 deletions.
17 changes: 16 additions & 1 deletion packages/actions/test/data/samples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,20 @@ const fakeUser2 = generateFakeUser({
}
})

const fakeUser3 = generateFakeUser({
uid: "0000000000000000000000000003",
data: {
name: "user3",
displayName: undefined,
creationTime: Date.now(),
lastSignInTime: Date.now() + 1,
lastUpdated: Date.now() + 2,
email: "[email protected]",
emailVerified: false,
photoURL: undefined
}
})

const fakeCeremonyScheduledFixed = generateFakeCeremony({
uid: "0000000000000000000A",
data: {
Expand Down Expand Up @@ -257,7 +271,8 @@ const fakeCircuitSmallContributors = generateFakeCircuit({

export const fakeUsersData = {
fakeUser1,
fakeUser2
fakeUser2,
fakeUser3
}

export const fakeCeremoniesData = {
Expand Down
100 changes: 100 additions & 0 deletions packages/actions/test/unit/security.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import chai, { expect } from "chai"
import chaiAsPromised from "chai-as-promised"
import { getAuth, signInWithEmailAndPassword } from "firebase/auth"
import {
createNewFirebaseUserWithEmailAndPw,
deleteAdminApp,
generatePseudoRandomStringOfNumbers,
initializeAdminServices,
initializeUserServices,
sleep,
addCoordinatorPrivileges
} from "../utils"
import { fakeUsersData } from "../data/samples"
import { getCurrentFirebaseAuthUser } from "../../src"
import { getDocumentById } from "../../src/helpers/query"

chai.use(chaiAsPromised)

describe("Security rules", () => {
// Init admin services.
const { adminFirestore, adminAuth } = initializeAdminServices()
const { userApp, userFirestore } = initializeUserServices()
const userAuth = getAuth(userApp)

const user1 = fakeUsersData.fakeUser1
const user2 = fakeUsersData.fakeUser2
const user3 = fakeUsersData.fakeUser3
const user1Pwd = generatePseudoRandomStringOfNumbers(24)
const user2Pwd = generatePseudoRandomStringOfNumbers(24)
const user3Pwd = generatePseudoRandomStringOfNumbers(24)

beforeAll(async () => {
// create 1st user
await createNewFirebaseUserWithEmailAndPw(userApp, user1.data.email, user1Pwd)

// Retrieve the current auth user in Firebase.
let currentAuthenticatedUser = getCurrentFirebaseAuthUser(userApp)
user1.uid = currentAuthenticatedUser.uid

// create 2nd user
await createNewFirebaseUserWithEmailAndPw(userApp, user2.data.email, user2Pwd)

// Retrieve the current auth user in Firebase.
currentAuthenticatedUser = getCurrentFirebaseAuthUser(userApp)
user2.uid = currentAuthenticatedUser.uid
await sleep(5000) // 5s delay.

// create the coordinator
await createNewFirebaseUserWithEmailAndPw(userApp, user3.data.email, user3Pwd)
currentAuthenticatedUser = getCurrentFirebaseAuthUser(userApp)
user3.uid = currentAuthenticatedUser.uid
await sleep(5000) // 5s delay.
})

it("should work as expected and return the data for the same user", async () => {
// login as user1
await signInWithEmailAndPassword(userAuth, user1.data.email, user1Pwd)
const userDoc = await getDocumentById(userFirestore, "users", user1.uid)
const data = userDoc.data()
expect(data).to.not.be.null
})

it("should not return another user's document", async () => {
// login as user2
await signInWithEmailAndPassword(userAuth, user2.data.email, user2Pwd)
// below should fail because we are trying to retrieve a document from another user.
expect(getDocumentById(userFirestore, "users", user1.uid)).to.be.rejectedWith(
"Missing or insufficient permissions."
)
})

it("should allow the coordinator to read another user's document", async () => {
// login as user3
await signInWithEmailAndPassword(userAuth, user3.data.email, user3Pwd)
// Retrieve the current auth user in Firebase.
const currentAuthenticatedUser = getCurrentFirebaseAuthUser(userApp)
// sleep
await sleep(5000)
// add coordinator privileges
await addCoordinatorPrivileges(adminAuth, user3.uid)
// force refresh
await currentAuthenticatedUser.getIdTokenResult(true)
// retrieve the document of another user
const userDoc = await getDocumentById(userFirestore, "users", user1.uid)
const data = userDoc.data()
expect(data).to.not.be.null
})
afterAll(async () => {
// Clean user from DB.
await adminFirestore.collection("users").doc(user1.uid).delete()
await adminFirestore.collection("users").doc(user2.uid).delete()
await adminFirestore.collection("users").doc(user3.uid).delete()
// Remove Auth user.
await adminAuth.deleteUser(user1.uid)
await adminAuth.deleteUser(user2.uid)
await adminAuth.deleteUser(user3.uid)
// Delete admin app.
await deleteAdminApp()
})
})
2 changes: 1 addition & 1 deletion packages/actions/test/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ export {
generatePseudoRandomStringOfNumbers
} from "./configs"

export { createNewFirebaseUserWithEmailAndPw } from "./authentication"
export { addCoordinatorPrivileges, createNewFirebaseUserWithEmailAndPw } from "./authentication"
19 changes: 16 additions & 3 deletions packages/backend/firestore.rules
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
rules_version = '2';
// Allow read/write access on all documents to any user signed in to the application
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth != null;
// Define which users can read and write to the database
match /users/{userId} {
// users can read update and delete their own data and coordinator can read, update, delete any user's data
allow read, update, delete:
if request.auth != null &&
request.auth.uid == userId ||
request.auth.token.coordinator;
// coordinator can create new users
allow create:
if request.auth != null && request.auth.token.coordinator;
}
match /ceremonies/{ceremonyId} {
// any authenticated user can read
allow read: if request.auth != null;
// only coordinator can create, update, delete ceremonies
allow create, update, delete: if request.auth != null && request.auth.token.coordinator;
}
}
}

0 comments on commit 7fb1c4a

Please sign in to comment.