utils/firebase
Krzysztof kuhy Rudnicki 4c024a24f0
Some checks failed
file length / file-length (push) Waiting to run
palette drift / palette (push) Waiting to run
sync_settings_ui tests / test (push) Has been cancelled
firebase: make the rules template impossible to deploy verbatim
database.rules.json carries YOUR_FIREBASE_UID on purpose -- this repo is
public and the uid is the one value that must not be in it. But nothing
stopped anyone publishing the template as-is, and rules pinned to a uid that
matches no account deny every read and write. The symptom is every device
failing at once with a bare permission error, which reads like an outage
rather than a bad paste.

render_rules.sh substitutes the uid from ~/.config/crdt-sync/firebase.json
and fails closed on anything unsafe: a surviving placeholder, missing
`auth != null`, a missing uid pin (that one matters -- `auth != null` alone
lets any stranger who self-registers through the public Web API key read
everything), or output that is not JSON.

Verified: its output is byte-identical to the deployed
~/.config/crdt-sync/database.rules.json, and the live database still answers
an unauthenticated read with 401. All three failure paths exercised.

Also corrects the README, which described the in-repo copy as "the deployed
rules, with the real uid filled in" -- it never was.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CFSGeWxHPY5rHUGrPqhcmG
2026-08-22 19:10:10 +02:00
..
database.rules.json crdt_sync: add a Firebase Realtime Database transport behind a RemoteStore seam 2026-08-03 18:14:25 +02:00
README.md firebase: make the rules template impossible to deploy verbatim 2026-08-22 19:10:10 +02:00
render_rules.sh firebase: make the rules template impossible to deploy verbatim 2026-08-22 19:10:10 +02:00

firebase/

Deployable configuration for the Firebase project backing crdt_sync / crdt_sync_dart sync. Language-neutral, so it lives here rather than under either library.

This repository is public. Nothing here names a concrete project, database URL, account or uid. Those live in a local, 0600 config file — see Local configuration.

Why Realtime Database, over REST

  • No SDK. Official FlutterFire has no Linux desktop support, and one caller (wake_alarm's PC side) is a dependency-free systemd job. The REST endpoints are plain HTTPS and behave identically on Linux, Android and headless.
  • No per-operation quota on the Spark (no-cost) plan. RTDB bills only storage and bandwidth, so a misbehaving sync loop cannot exhaust a daily budget and silently stop working mid-day. Firestore's 20k-writes/day cap would be a second way to hit a wall, for querying this workload never does.
  • Path model matches the existing layout — <prefix>/<deviceId>/<file> — so no data remodelling.

Deliberately not used: Cloud Storage (requires a linked billing account since 2026‑02‑03) and Cloud Functions. Avoiding both keeps the project structurally incapable of generating a bill.

database.rules.json

Scopes the whole database to a single Firebase Auth uid.

auth != null alone would not be safe: email/password sign-up is open through the identitytoolkit REST API to anyone holding the Web API key, and that key ships inside the Android APKs. Pinning the uid means a stranger who registers an account still reads and writes nothing.

The uid here is a placeholder on purpose — this repo is public, and the uid is the one value that must not appear in it. Publishing this file verbatim yields rules matching no account, which locks every app out of the project until someone notices; the failure looks like a permission error from every device at once.

So do not hand-substitute. Render it:

./render_rules.sh            # prints deployable rules, real uid filled in
./render_rules.sh --check    # validates without printing the uid

It reads the uid from ~/.config/crdt-sync/firebase.json and refuses to emit anything still containing the placeholder, missing auth != null, missing a uid pin, or not parsing as JSON. Paste its output into Realtime Database → Rules → Publish.

Verify the rules are live — an unauthenticated request must be refused:

curl -s "$DATABASE_URL/.json"
# {"error" : "Permission denied"}   <- expected

Local configuration

Everything environment-specific lives in ~/.config/crdt-sync/, mode 0600, outside version control:

File Contents
firebase.json apiKey, databaseUrl, projectId, uid, email
password the sync account's password, no trailing newline
database.rules.json the rendered rules, real uid filled in (see render_rules.sh)

The Web API key is not a secret — it is a public project identifier that ships inside client apps. The password and refresh tokens are the real secrets.

Credentials at runtime

No service-account key is used anywhere: it would bypass the security rules entirely and is trivially extractable from an installed APK. Devices sign in as the single user and hold a refresh token:

  • Android — flutter_secure_storage
  • Linux — ~/.config/<app>/firebase.json, mode 0600

ID tokens last about an hour, so the refresh exchange is mandatory rather than optional — diet_guard's PC timer alone runs 96 times a day.