The Firestore security rules for every Huishouden app, with their emulator tests. Firebase allows one rules file per project, so all apps share this one; it is the boundary that keeps each household's information visible only to its members.
firestore.rules: who may read and write what. Everything lives underhouseholds/{householdId}; members are listed on the household document by lowercase email.firestore.indexes.json: indexes the apps' queries need.test/rules/: emulator tests proving each rule (allowed access succeeds, the rest fails).
An app that adds or changes data opens a pull request here with the rules block and tests next to
the others. CI runs the tests against the Firestore emulator; merging to main deploys the rules
and indexes (keyless, via the suite's deploy identity) only after the tests pass.
Once the tests pass, every same-repo pull request also deploys its rules to the staging project
(huishouden-staging, invented data only), so an app's PR can be tried on staging against rules
that haven't merged yet; main deploys to staging too, so staging returns to the merged rules.
Staging carries whichever rules were deployed last. The staging deploy runs only once the repo has
the STAGING_GCP_* variables, which pwa-kit's bootstrap.sh --staging sets. See pwa-kit
STANDARD.md "Staging".
bun install
bun run test # needs Java 21 for the emulatorEvery block follows the same pattern: who may read and write (by role, below), an exact field
list (keys().hasOnly([...])), and type and size checks on each field.
Health keeps people's medicines, which only the household's admins and the person's carers read:
| Path | Fields |
|---|---|
healthPeople/{person} |
name, birthDate, email (when the person is a member), carers, readers (the carers and the person), allergies, notes, createdAt, updatedAt, by |
healthPeople/{person}/photo/avatar |
data (WebP or JPEG data URL), updatedAt, by |
healthPeople/{person}/meds/{med} |
personId (the path's), name, strength, dose, doseAmount, doseUnit, asNeeded, times, everyDays, rule, minHours, maxPerDay, withFood, startDate, endDate, prescriberId, pharmacyId, refills, supply, supplyAt, refillOrderedAt, escalateMinutes, remind, notes, createdAt, updatedAt, by |
healthPeople/{person}/doses/{dose} |
personId (the path's), medId, slot (YYYY-MM-DDTHH:MM, none when as needed), at, status (given, skipped), note, by, createdAt |
Everything under a person is checked against the person document by its path, so list queries
work: admins list healthPeople whole, everyone else with where('readers', 'array-contains', me).
Kids never read health data, even if named.
personalAgenda, personalTodos and personalReminders hold the agenda items, to-dos and
reminders for named members only (@huishouden/pwa-kit/audience): each names audience, and only
those members read, write (in their own name, among the audience) and remove it; queries ask for
where('audience', 'array-contains', me). The shared sender reads personalReminders with the
collection-group indexes in firestore.indexes.json.
Each member has a role in the household document's roles map ({ "<email>": "admin" }). Anyone
it doesn't name is a member, except the household's creator (first in members), who is an admin.
@huishouden/pwa-kit/roles has the same table for the apps (householdRole, can, useRole).
| Admin | Member | Helper | Kid | |
|---|---|---|---|---|
| Invite and remove people, set roles (never their own) | yes | |||
| Rename the household; settings, food preferences, portal layout, lists, cars, meal plan, medicine courses | yes | yes | ||
| Read lists, chores, pets, baby, home, car, contacts and appointments | yes | yes | yes | yes |
| Add items and log feeds, sleep, diapers, meals, readings and visits | yes | yes | yes | yes |
| Tick off anyone's item, chore, job, reminder or service, and Home's things to do before a regular event; end anyone's baby sleep | yes | yes | yes | yes |
| Change or delete what someone else added | yes | yes | own only | own only |
| Give pet medicine (dose logs) | yes | yes | if the course allows them | |
| Read or write Spending and Bills | yes | yes | ||
| Read contacts, appointments and agenda items marked private | yes | yes | ||
| Health: read a person, their medicines and doses | yes | if a carer (or it is them) | if a carer | |
| Health: add or change a person and their medicines | yes | if a carer | ||
| Health: record a dose given or skipped; mark a refill ordered | yes | if a carer | if a carer (change own doses only) | |
| Personal agenda items, to-dos and reminders | if named in audience |
if named | if named |
- Helpers and kids add records in their own name (
byis their email) and may change or delete only those; on anyone's record they may change only the fields that tick it off. - A medicine course's
giversisall(every helper, the default) orapproved(only the helpers inapprovedHelpers). private: truehides a contact, appointment, agenda item or reminder from helpers and kids. A record without the flag is private to them until it is written withprivate: false, so their queries ask forprivate == false; the apps write the flag on every save.- The to-do list (
todos) is kept in step the same way. Each item's Done and Cancel are writes the portal makes as the member who taps them, so each is checked by its own collection's rules here. Cancelled things stay in their app's history: a cancelled to-do (cancelledAt), a paused Home job or car service (pausedAt), a closed car renewal (closedAt), a skipped baby checklist item, Home prep task or pet medicine dose (skipped), a dismissed pet reminder (dismissedAt), a skipped bill (dismissed). Setting them changes the record, so helpers and kids may do it only on what they added. - Reminders and agenda items are kept in step by whichever device opens an app, so helpers and kids may write the open ones, signed by them, linking only into the apps and never re-arming a sent reminder. Spending's and Bills' are always private, whoever writes them.
- Home's regular events (
homeEvents) are everyday records: anyone adds their own; moving or skipping one occurrence (exceptions) changes the event, so it is for admins, members and whoever added it. Ticking off the thing to do before an occurrence (homeEventPrep,<eventId>_<day>) is open to everyone in their own name; Undo removes your own tick. - Kids never tick off a medicine reminder (flea and tick, heartworm, deworming, medication): that records it given. Ticking a step on someone else's item keeps the number of steps.