mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-10 20:47:15 +01:00
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
88 lines
3.5 KiB
Markdown
88 lines
3.5 KiB
Markdown
# 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.
|