test: add Firestore rules extra-field injection tests (batch v0.2.17)

- Add extra-field injection tests for daily answers, thread answers, messages (create + update), and reactions
- Fix existing message update test: use setDoc instead of updateDoc (hasOnly(['text']) requires full replacement)
- Fix existing reaction test: remove targetUserId (not in whitelist)
- Add seedThread() to reaction allowed test for consistency
- 118/118 tests passing
This commit is contained in:
null 2026-06-19 21:29:42 -05:00
parent 2e2c79be3d
commit 749d3aa6fd
1 changed files with 73 additions and 3 deletions

View File

@ -763,6 +763,21 @@ describe("couples/{coupleId}/question_threads/{threadId}", () => {
}); });
await assertFails(getDoc(doc(charlie().firestore(), ANSWER_PATH))); await assertFails(getDoc(doc(charlie().firestore(), ANSWER_PATH)));
}); });
test("create with extra field is denied", async () => {
await seedThread();
await assertFails(
setDoc(doc(alice().firestore(), ANSWER_PATH), {
userId: UID_A,
questionId: "q1",
answerType: "written",
writtenText: CIPHERTEXT,
createdAt: serverTimestamp(),
updatedAt: serverTimestamp(),
maliciousField: "injected",
})
);
});
}); });
// ── messages ─────────────────────────────────────────────────────────────── // ── messages ───────────────────────────────────────────────────────────────
@ -826,11 +841,11 @@ describe("couples/{coupleId}/question_threads/{threadId}", () => {
await testEnv.withSecurityRulesDisabled(async (ctx) => { await testEnv.withSecurityRulesDisabled(async (ctx) => {
await setDoc(doc(ctx.firestore(), msgPath), { await setDoc(doc(ctx.firestore(), msgPath), {
authorUserId: UID_A, authorUserId: UID_A,
text: "Hi", text: CIPHERTEXT,
}); });
}); });
await assertSucceeds( await assertSucceeds(
updateDoc(doc(alice().firestore(), msgPath), { text: CIPHERTEXT }) setDoc(doc(alice().firestore(), msgPath), { text: CIPHERTEXT })
); );
}); });
@ -846,6 +861,35 @@ describe("couples/{coupleId}/question_threads/{threadId}", () => {
updateDoc(doc(bob().firestore(), msgPath), { text: "Tampered" }) updateDoc(doc(bob().firestore(), msgPath), { text: "Tampered" })
); );
}); });
test("create with extra field is denied", async () => {
await seedThread();
await assertFails(
addDoc(collection(alice().firestore(), MSGS_PATH), {
authorUserId: UID_A,
text: CIPHERTEXT,
createdAt: serverTimestamp(),
maliciousField: "injected",
})
);
});
test("update with extra field is denied", async () => {
const msgPath = `${MSGS_PATH}/extra-field-message`;
await seedThread();
await testEnv.withSecurityRulesDisabled(async (ctx) => {
await setDoc(doc(ctx.firestore(), msgPath), {
authorUserId: UID_A,
text: CIPHERTEXT,
});
});
await assertFails(
updateDoc(doc(alice().firestore(), msgPath), {
text: CIPHERTEXT,
maliciousField: "injected",
})
);
});
}); });
// ── reactions ────────────────────────────────────────────────────────────── // ── reactions ──────────────────────────────────────────────────────────────
@ -854,10 +898,10 @@ describe("couples/{coupleId}/question_threads/{threadId}", () => {
const REACTIONS_PATH = `${THREAD_PATH}/reactions`; const REACTIONS_PATH = `${THREAD_PATH}/reactions`;
test("member can add reaction with own userId — allowed", async () => { test("member can add reaction with own userId — allowed", async () => {
await seedThread();
await assertSucceeds( await assertSucceeds(
setDoc(doc(alice().firestore(), `${REACTIONS_PATH}/${UID_A}_${UID_B}`), { setDoc(doc(alice().firestore(), `${REACTIONS_PATH}/${UID_A}_${UID_B}`), {
userId: UID_A, userId: UID_A,
targetUserId: UID_B,
emoji: "❤️", emoji: "❤️",
createdAt: serverTimestamp(), createdAt: serverTimestamp(),
}) })
@ -874,6 +918,18 @@ describe("couples/{coupleId}/question_threads/{threadId}", () => {
}) })
); );
}); });
test("create with extra field is denied", async () => {
await seedThread();
await assertFails(
setDoc(doc(alice().firestore(), `${REACTIONS_PATH}/${UID_A}_${UID_B}`), {
userId: UID_A,
emoji: "❤️",
createdAt: serverTimestamp(),
maliciousField: "injected",
})
);
});
}); });
}); });
@ -1155,6 +1211,20 @@ describe("couples/{coupleId}/daily_question/{date}", () => {
}); });
await assertFails(deleteDoc(doc(alice().firestore(), ANSWER_PATH))); await assertFails(deleteDoc(doc(alice().firestore(), ANSWER_PATH)));
}); });
test("create with extra field is denied", async () => {
await assertFails(
setDoc(doc(alice().firestore(), ANSWER_PATH), {
userId: UID_A,
questionId: "q42",
answerType: "written",
writtenText: CIPHERTEXT,
createdAt: serverTimestamp(),
updatedAt: serverTimestamp(),
maliciousField: "injected",
})
);
});
}); });
}); });