Build Guide / 05
Database & security
There's no traditional SQL schema here — the database is Cloud Firestore,
and the schema effectively lives in firestore.rules, which is
both the access-control layer and the closest thing to a data contract.
Firestore as the system of record
Collections are read and written directly from the React client using the
Firebase v11 SDK (src/lib/firebase.ts), with real-time listeners
(onSnapshot) used anywhere the UI needs to reflect changes made
elsewhere — a bed becoming free, a new lab result, a notification.
Writes that must bypass per-user rules (like the daily bed-charge cron) go
through firebase-admin on the server instead.
How access control is modeled
firestore.rules defines a small set of reusable predicates that
every collection's rules are built from:
function isSignedIn() {
return request.auth != null;
}
function isEmailVerified() {
return isSignedIn() && request.auth.token.email_verified == true;
}
function getUserData() {
return (isSignedIn() && exists(/databases/$(database)/documents/users/$(request.auth.uid)))
? get(/databases/$(database)/documents/users/$(request.auth.uid)).data
: null;
}
function hasRole(roleName) {
return isSignedIn() && exists(/databases/$(database)/documents/users/$(request.auth.uid)) && ...
}
Every collection's rule is then written in terms of these — "signed in and has role X" or "signed in and is the owner of this document" — rather than repeating auth logic per collection. The rules file opens with an explicit default-deny for anything unmatched, so a new collection is inaccessible until a rule is written for it.
Storage
storage.rules governs Firebase Storage the same way, for
anything uploaded rather than written as structured data — scanned
documents, DICOM images, profile photos.
Testing the rules
test/firestore.rules.test.ts uses
@firebase/rules-unit-testing to exercise the rules directly
against the Firestore emulator — asserting that a nurse can't read
accounts data, a patient can't read another patient's record, and so on
— independent of the application code.
Defense in depth on the client
Firestore rules are the actual security boundary, but the app doesn't stop
there: dompurify sanitizes any HTML rendered from stored
strings, and the audit logger in src/lib/auditLogger.ts records
who changed what, which matters as much for accountability in a hospital
setting as it does for debugging.
Firestore security rules are the single most important file to get right
in this stack — a mistake there is a data leak, not a bug report. Any
change to firestore.rules should be run against the rules test
suite before it ships.