mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-10 16:48:54 +01:00
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
This commit is contained in:
parent
db3d8f827f
commit
4c024a24f0
@ -34,8 +34,23 @@ 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.
|
||||
|
||||
To deploy, substitute the real uid (Firebase console → Authentication → Users
|
||||
→ User UID) and paste into **Realtime Database → Rules → Publish**.
|
||||
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:
|
||||
|
||||
@ -53,7 +68,7 @@ outside version control:
|
||||
|---|---|
|
||||
| `firebase.json` | `apiKey`, `databaseUrl`, `projectId`, `uid`, `email` |
|
||||
| `password` | the sync account's password, no trailing newline |
|
||||
| `database.rules.json` | the deployed rules, with the real uid filled in |
|
||||
| `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
|
||||
|
||||
85
firebase/render_rules.sh
Executable file
85
firebase/render_rules.sh
Executable file
@ -0,0 +1,85 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ============================================================================
|
||||
# Render deployable Realtime Database rules from the public template.
|
||||
#
|
||||
# The template in this repo carries a placeholder on purpose: the repository
|
||||
# is public and the uid is the one value that must not appear in it. That
|
||||
# leaves a trap -- publishing the template verbatim yields rules that match
|
||||
# no account, which locks every app out of the shared project until someone
|
||||
# notices. This script closes it: it substitutes the real uid from the local
|
||||
# config and refuses to emit anything still containing a placeholder.
|
||||
# ============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
readonly SCRIPT_NAME
|
||||
readonly SCRIPT_DIR
|
||||
readonly TEMPLATE="$SCRIPT_DIR/database.rules.json"
|
||||
readonly CONFIG_DIR="${CRDT_SYNC_CONFIG_DIR:-$HOME/.config/crdt-sync}"
|
||||
readonly PLACEHOLDER="YOUR_FIREBASE_UID"
|
||||
|
||||
usage() {
|
||||
echo "Usage: $SCRIPT_NAME [--check]"
|
||||
echo " (no args) print deployable rules with the real uid substituted"
|
||||
echo " --check verify the rendered rules are sane; print nothing"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# The uid lives in firebase.json, the single source of truth for this machine.
|
||||
read_uid() {
|
||||
local config="$CONFIG_DIR/firebase.json"
|
||||
if [[ ! -r "$config" ]]; then
|
||||
echo "Error: $config is missing or unreadable." >&2
|
||||
echo "It holds the uid these rules pin; see README.md." >&2
|
||||
exit 1
|
||||
fi
|
||||
python3 -c "import json,sys; print(json.load(open(sys.argv[1]))['uid'])" \
|
||||
"$config"
|
||||
}
|
||||
|
||||
render() {
|
||||
local uid="$1"
|
||||
sed "s/$PLACEHOLDER/$uid/g" "$TEMPLATE"
|
||||
}
|
||||
|
||||
# Fail closed: a rules file that still names the placeholder, or that any
|
||||
# unauthenticated request would satisfy, must never reach the console.
|
||||
validate() {
|
||||
local rendered="$1"
|
||||
if [[ "$rendered" == *"$PLACEHOLDER"* ]]; then
|
||||
echo "Error: rendered rules still contain $PLACEHOLDER." >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -q 'auth != null' <<< "$rendered"; then
|
||||
echo "Error: rendered rules do not require authentication." >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -q "auth.uid ===" <<< "$rendered"; then
|
||||
echo "Error: rendered rules do not pin a uid; 'auth != null' alone" >&2
|
||||
echo "lets any stranger who self-registers read and write." >&2
|
||||
exit 1
|
||||
fi
|
||||
python3 -c "import json,sys; json.loads(sys.stdin.read())" <<< "$rendered"
|
||||
}
|
||||
|
||||
main() {
|
||||
local check_only=false
|
||||
[[ "${1:-}" == "--check" ]] && check_only=true
|
||||
[[ "${1:-}" == "-h" || "${1:-}" == "--help" ]] && usage
|
||||
|
||||
local uid rendered
|
||||
uid="$(read_uid)"
|
||||
rendered="$(render "$uid")"
|
||||
validate "$rendered"
|
||||
|
||||
if [[ "$check_only" == true ]]; then
|
||||
echo "Rules render cleanly and pin a uid." >&2
|
||||
else
|
||||
printf '%s\n' "$rendered"
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Loading…
Reference in New Issue
Block a user