This document is for the repo owner / offsite organiser. The Step Tracker
treats cloud sync as always-on when configured: every visitor is signed
in anonymously, their snapshot is mirrored to Firestore in the background,
and offline-resilience is provided by the Firestore SDK's persistent local
cache. There is no end-user opt-in toggle — the only way to "disable" it is
to ship with src/firebase-config.ts empty, in which case the app falls
back to a purely local experience.
The Firebase project for the offsite is:
- Project ID:
cxeemeastep - Console: https://console.firebase.google.com/project/cxeemeastep/overview
- Open the project in the Firebase Console.
- Go to Build → Firestore Database → Create database.
- Start in Production mode (we ship explicit rules — see step 4).
- Choose location
eur3(multi-region Europe). This must match the region picked at project creation; if you previously chose a different region, just keep the existing one. - Click Enable.
The app signs each user in anonymously so per-user write rules can be enforced without anyone making accounts.
- Build → Authentication → Get started.
- Open the Sign-in method tab.
- Enable Anonymous and save.
-
Project settings (gear icon) → General → Your apps.
-
Click the
</>Web icon to register a new web app (e.g. name itstep-tracker-web). You do not need Firebase Hosting. -
Firebase shows a
firebaseConfigobject. The values are read at build time from environment variables — do not paste them into source.For local development — copy
.env.exampleto.env.local(already gitignored) and fill in:cp .env.example .env.local
VITE_FIREBASE_API_KEY=AIza… VITE_FIREBASE_AUTH_DOMAIN=cxeemeastep.firebaseapp.com VITE_FIREBASE_PROJECT_ID=cxeemeastep VITE_FIREBASE_STORAGE_BUCKET=cxeemeastep.firebasestorage.app VITE_FIREBASE_MESSAGING_SENDER_ID=… VITE_FIREBASE_APP_ID=1:…:web:… VITE_FIREBASE_MEASUREMENT_ID=G-…
For Azure Static Web Apps CI — open Repo → Settings → Secrets and variables → Actions → New repository secret and add each variable above as a separate secret with the same name. The
.github/workflows/azure-static-web-apps-*.ymlworkflow passes them into the Oryx-driven Vite build via the deploy step'senv:block. -
Once
VITE_FIREBASE_API_KEYandVITE_FIREBASE_APP_IDare present at build time,isFirebaseConfigured()returnstrueand cloud sync runs automatically for every visitor. Without them, the app falls back to a purely local experience.
Firebase web API keys are technically not secrets — they identify the
project, and real authorisation is enforced by firestore.rules. But
GitHub's secret scanner and most security tooling will flag a AIza… key
in the source tree, which generates noise and is a bad pattern to model
for forks. Keep them in env vars and lock them down at the platform layer:
- HTTP referrer restrictions — In the
Google Cloud Console → APIs & Services → Credentials,
open the Firebase browser key and set Application restrictions →
Websites to:
https://gentle-cliff-07e205d03.7.azurestaticapps.net/*← required (browsers send the bare origin asRefererdue to the defaultstrict-origin-when-cross-originpolicy, so a narrower path-scoped pattern would reject every real browser request with HTTP 403)https://gentle-cliff-07e205d03.7.azurestaticapps.net(some browsers send Referer with no trailing slash)- Any custom domain you wire up to the Static Web App, in both
https://example.com/*and bare-origin variants. http://localhost:5173/*andhttp://localhost:5173(local dev)
- API restrictions — Limit the key to the APIs the app actually needs: Identity Toolkit API, Cloud Firestore API, Firebase Installations API, Token Service API.
With these restrictions in place, even a leaked key cannot be used from an unauthorised origin.
Treat it as compromised regardless of its public-by-design nature:
- Rotate it. GCP Console → Credentials → ⋮ next to the browser key
→ Regenerate key. Update the value in repo secrets and
.env.local. - Apply the restrictions above to the new key before redeploying.
- Optionally scrub git history with
git filter-repoand force-push, so the old key no longer appears in commit diffs.
The repo ships a firestore.rules file that:
- allows any signed-in user (incl. anonymous) to read
users/*(so the leaderboard can list everyone), - allows a user to create / update only their own
users/{uid}doc, - validates the doc shape (name, team, goal, entries),
- forbids client-side deletes.
To deploy:
# one-time, globally
npm install -g firebase-tools
# from the repo root
firebase login
firebase use cxeemeastep
# deploy only the Firestore rules
firebase deploy --only firestore:rulesIf firebase use complains about no project being configured locally, you
can pass it inline instead:
firebase deploy --only firestore:rules --project cxeemeastepYou should see the rules update in the Firebase Console under Firestore Database → Rules.
Once the config is filled in and rules are deployed, no user action is required:
- A visitor opens the app — the Firebase SDK initialises with an IndexedDB-backed persistent local cache.
- They are signed in anonymously in the background. The anonymous UID is cached in IndexedDB and survives reloads (even offline) on subsequent visits.
- Their current snapshot (
name,team,goal,entries) is written tousers/{uid}. - Any subsequent change to their profile or step entries is mirrored to Firestore in the background (debounced ~600 ms).
- If they go offline, writes are buffered locally by the SDK and flushed automatically once the connection returns. The Profile → Cloud sync panel reflects the live state (Synced / Offline — saving locally / Connecting…).
The following leaves each user's device:
- their display name (free-form, ≤ 60 chars),
- their team selection,
- their daily step goal,
- their per-day step counts keyed by ISO date.
Nothing else. No device IDs, no IPs beyond the standard Firebase request
metadata, no email, no location. If you ship a build with
src/firebase-config.ts left empty, the app behaves as a purely local
build — no Firebase code talks to the network.
After the offsite, you can wipe centrally-stored data with:
# Manually in the console:
# Firestore Database → users → ⋮ → Delete collection…or via the Admin SDK in a small script. Aim for ≤ 14 days post-event, consistent with GDPR storage-limitation principles.
"Cloud not configured" pill on Profile.
The VITE_FIREBASE_* environment variables aren't reaching the build.
For local dev, copy .env.example to .env.local and fill it in. For
the Azure Static Web Apps build, make sure the matching repo secrets
exist (Settings → Secrets and variables → Actions) and that they are
listed in the deploy step's env: block in
.github/workflows/azure-static-web-apps-*.yml. Re-run the workflow
after any secret change.
"Retrying…" pill with auth/network-request-failed or Requests from referer … are blocked in the browser console.
The API key's HTTP-referrer restriction is too narrow. Browsers default
to Referrer-Policy: strict-origin-when-cross-origin, which sends only
the origin (e.g. https://gentle-cliff-07e205d03.7.azurestaticapps.net/)
as Referer, not the full path. A path-scoped pattern will
therefore reject every real browser request with HTTP 403. Fix by
adding the bare-origin and /* variants of the deployed hostname (and
any custom domain) to the allowed websites in
GCP Credentials.
Changes propagate within a couple of minutes.
"Connecting…" never resolves; console shows auth/admin-restricted-operation.
Anonymous authentication isn't enabled. Re-do step 2.
Console shows permission-denied on setDoc.
The security rules either weren't deployed or the doc shape changed. Make
sure you ran firebase deploy --only firestore:rules and that the doc
keys are exactly name, team, goal, entries, updatedAt.
Leaderboard subscription returns nothing.
Check that at least one user has toggled cloud sync on. The users
collection only contains documents for users who've opted in.