utils/firebase/README.md
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

88 lines
3.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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](#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:
```bash
./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:
```bash
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.