From fe300f11d8f550e098cd9fc4860a23bdd46c07a6 Mon Sep 17 00:00:00 2001 From: Dave Liu <7david12liu@gmail.com> Date: Mon, 13 Jul 2026 00:28:09 -0700 Subject: [PATCH 1/9] SAFETY-001: isolate callbacks and local Firebase --- IMPLEMENTATION_PLAN.md | 6 +- SYSTEM_DESIGN.md | 45 +++- functions/package.json | 6 +- package.json | 2 + public/404.html | 7 +- public/index.html | 14 +- public/spa-navigation.js | 95 ++++++++ .../admin/events/AdminEventRegistrations.tsx | 4 +- .../firebase/FirebaseResources.test.ts | 219 ++++++++++++++++++ src/services/firebase/FirebaseResources.ts | 75 ++++-- src/services/monitoring/sentry.test.ts | 70 ++++++ src/services/monitoring/sentry.ts | 28 ++- tests/spa-navigation.test.js | 149 ++++++++++++ 13 files changed, 672 insertions(+), 48 deletions(-) create mode 100644 public/spa-navigation.js create mode 100644 src/services/firebase/FirebaseResources.test.ts create mode 100644 src/services/monitoring/sentry.test.ts create mode 100644 tests/spa-navigation.test.js diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md index b1f38c44..96a29007 100644 --- a/IMPLEMENTATION_PLAN.md +++ b/IMPLEMENTATION_PLAN.md @@ -92,7 +92,7 @@ Exit gate: - Unused vulnerable dependency chains are removed and direct safe patches applied. - Live checkout remains disabled and placeholder policies are visible as a launch blocker. -**Assessment snapshot, not current `main`:** the combined working tree contains a locally verified SAFETY-001 candidate, but issue [#99](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/99) is queued behind #104. Until #99 is isolated, reviewed, merged, and reverified, callback/emulator behavior is **NOT AVAILABLE YET**. +**Current source boundary:** issue [#99](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/99) provides the isolated callback and local Firebase safety slice: a same-origin path/query/fragment bridge, a fully synthetic local Firebase namespace, loopback Auth/Firestore/Functions routing, fail-closed setup, and local monitoring shutdown. Its exact issue/PR record remains the status source. This does not create staging, isolate provider calls made by Functions, prove a production callback, or repair a member profile. ### Phase 1 — Establish trust boundaries @@ -206,9 +206,9 @@ Use structured correlation across checkout request, business record, Stripe Sess The first tranche deliberately combines one urgent safety repair with the most complex payment foundation: -1. **SAFETY-001 — callback and emulator isolation:** preserve path/query/hash through GitHub Pages and connect local Functions to the emulator. This prevents paid/OAuth callbacks from breaking and local code from reaching production unexpectedly. +1. **SAFETY-001 — callback and emulator isolation:** #99 supplies the source/test boundary for same-origin callback preservation and loopback Auth/Firestore/Functions development. Provider calls, optimized previews, staging, and live callbacks remain separately gated. 2. **PAY-003 — webhook event inbox and safe reducer:** add event replay protection, async-aware payment confirmation, expected total/currency validation, direct metadata lookup, terminal-state guards, and lifecycle tests. -3. **SEC-001 — Firestore trust boundaries:** remove recursive browser-admin authority, protect secrets/financial state, retain only allowlisted catalog administration, and reverse unsafe rule tests. This is implemented locally and awaits review/deployment evidence. +3. **SEC-001 — Firestore trust boundaries:** #100 merged through [PR #123](https://github.com/Run-MPRC/Run-MPRC.github.io/pull/123) at `a7fc301e85b0aeabe396e771faea21d3fc8e7b2b`. Source and 295 Rules tests remove recursive browser-admin authority. Firebase deployment explicitly skipped, so deployed/live Rules remain unproven under #105. 4. **PROMO-001 — fail-safe monetary policy:** disable promotion entry and quarantine unexplained discount/tax/shipping adjustments. This safety default is implemented locally; creator tests/provider inventory remain. 5. **Identity/config next:** AUTH-001A [#98](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/98) merged at `ce22c110`, proving source/tests for unverified-target rejection at the existing role-grant endpoints. Its Firebase deployment skipped, so backend-live status is unproven and the remaining AUTH-001 parent work stays open. Continue CONFIG-001 and the uncompleted AUTH-001 guards/Rules/refresh/audit scope without duplicating #98. 6. **Then:** PAY-001/PAY-002 so checkout creation, refunds, and every later handler share strict schemas and idempotent state. diff --git a/SYSTEM_DESIGN.md b/SYSTEM_DESIGN.md index a699267e..18997b18 100644 --- a/SYSTEM_DESIGN.md +++ b/SYSTEM_DESIGN.md @@ -87,11 +87,30 @@ flowchart LR | Payments | Stripe Checkout Sessions, Payment Links, refunds, signed webhook | `functions/createCheckoutSession.js`, `createMerchCheckout.js`, `stripeWebhook.js` | Not ready for live payments until P0 issues are complete. | | Hosting | Netlify currently answers `runmprc.com`; GitHub Actions also publishes a separate Pages copy | `netlify.toml`, `.github/workflows/deploy.yml`, `public/404.html` | Split and drifting as verified 2026-07-12. A Pages success does not prove the live Netlify site changed. Hosting authority, preview, DNS, headers, and rollback remain open CI/WEB work. | | Email | Firestore `mail` outbox designed for the Firebase Trigger Email extension | `functions/sendConfirmationEmail.js` | Extension/provider deployment is unverified; outbox creation is not transactionally idempotent and HTML needs escaping. | -| Observability | Optional Sentry and Firebase Analytics | `src/services/monitoring`, `src/services/analytics` | Local initialization is disabled in the working tree; hosted policy/config is unverified and no payment-specific alerts, metrics, or reconciliation dashboard exists. | +| Observability | Optional Sentry and Firebase Analytics | `src/services/monitoring`, `src/services/analytics` | Local/test initialization is disabled by #99 source. Hosted redaction, replay, consent, retention, and provider configuration remain unverified under #111. | | Third-party fitness | Strava OAuth tokens and statistics | `functions/strava.js`, `src/services/strava` | Functional prototype. Working-tree Rules deny browser token access, but transactional refresh, scopes/revocation, IAM/encryption decision, and audit remain OAUTH-001. | The repository workflow presents GitHub Pages plus Firebase as a release path, but public DNS and response headers show that the live custom domain is served by Netlify. The two copies currently contain different bundles. The Firebase job can also finish green while explicitly skipping deployment when `FIREBASE_SERVICE_ACCOUNT` is absent. Treat Pages publication, Netlify production, and Firebase deployment as separate evidence until one hosting/release authority is chosen. The App Engine synchronization script is another surface that must be documented as active or retired. +### GitHub Pages callback handoff + +```mermaid +flowchart LR + Provider["Stripe or Strava returns to a deep link"] + Missing["GitHub Pages 404"] + Store["Store same-origin path, query, and fragment\nin this browser tab"] + Root["Redirect to the site root"] + Restore["Remove temporary state, validate origin,\nand restore the route once"] + Router["React Router opens the callback page"] + Reject["Discard malformed or outside-site target"] + + Provider --> Missing --> Store --> Root --> Restore + Restore -->|safe| Router + Restore -->|unsafe| Reject +``` + +Text alternative: the 404 page temporarily carries the complete return route to the root page; the root page deletes that temporary value before accepting only a same-origin route. The bridge does not prove payment, OAuth state, or identity. Server/provider verification still decides the result. + ## 4. Target deployment topology The target keeps React, Firebase, and Stripe, but places stronger boundaries around them. Migrating the frontend from GitHub Pages to Firebase Hosting is recommended before live commerce because it supports controlled SPA rewrites, preview channels, and security headers. That hosting migration is not required to design or test the backend, and must be executed as its own issue. @@ -296,12 +315,32 @@ Returning a `2xx` response to Stripe means the event has been durably accepted o | Environment | Firebase | Stripe | Domain | Data policy | | --- | --- | --- | --- | --- | -| Local | Firebase Emulator Suite | Stripe CLI and test objects | `localhost` | Synthetic data only; no production network fallback | +| Local source runtime | Firebase Emulator Suite under `demo-mprc-local` | Not safe by Firebase emulation alone | `localhost:3000` | Synthetic data only; browser Firebase traffic is loopback-only | | CI | Ephemeral emulators and mocks | Stripe fixtures/signature tests; optional isolated test account | None | Synthetic data, no production secrets | | Staging | Dedicated non-production Firebase project | Stripe sandbox/test keys and its own webhook endpoint | `dev.runmprc.com` | Synthetic or consented test records only | | Production | Dedicated production Firebase project | Live restricted keys and production webhook secret | `runmprc.com` | Real data under documented retention and access policies | -`SITE_ORIGIN`, Firebase project IDs, App Check keys, Stripe keys, webhook endpoints, Sentry environments, and email providers must be environment-scoped. Live and test webhook secrets are never interchangeable. The current repository points to one Firebase project and needs an explicit staging project before end-to-end rollout. +```mermaid +flowchart LR + Browser["Local React app\nlocalhost:3000"] + Auth["Auth emulator\n127.0.0.1:9099"] + Firestore["Firestore emulator\n127.0.0.1:8080"] + Functions["Functions emulator\n127.0.0.1:5001"] + Providers["Stripe, Strava, email,\nand other outside providers"] + + Browser --> Auth + Browser --> Firestore + Browser --> Functions + Functions -. "NOT isolated by Firebase" .-> Providers +``` + +Text alternative: the local browser uses only the three loopback Firebase emulators. A Function can still call an outside provider, so Firebase emulator readiness alone does not authorize checkout, refund, email, or Strava testing. + +#99 source selects a fully synthetic Firebase configuration for development/test, connects Auth, Firestore, and Functions to the loopback ports above, stops startup when connection setup fails, and leaves App Check, Analytics, and Sentry off locally. The Firebase CLI must still report all three emulators ready before the app is opened. Mocked endpoint tests alone do not prove listening processes. + +Optimized builds—including current Netlify previews and a locally served `build/` directory—use `NODE_ENV=production` and still target production Firebase. They are restricted to public, read-only visual review until #105/CONFIG establishes a separate staging configuration. Do not sign in, open private/admin pages, or test Firebase/provider actions in those previews. + +`SITE_ORIGIN`, Firebase project IDs, App Check keys, Stripe keys, webhook endpoints, Sentry environments, and email providers must be environment-scoped. Live and test webhook secrets are never interchangeable. The repository still needs an explicit staging project before end-to-end rollout. ## 11. Security, privacy, and compliance posture diff --git a/functions/package.json b/functions/package.json index 892fcb59..5784ad38 100644 --- a/functions/package.json +++ b/functions/package.json @@ -3,15 +3,15 @@ "description": "Cloud Functions for Firebase - MPRC Backend", "scripts": { "build": "echo 'no build step'", - "serve": "firebase emulators:start --only functions", - "shell": "firebase functions:shell", + "serve": "firebase emulators:start --project demo-mprc-local --only functions", + "shell": "firebase functions:shell --project demo-mprc-local", "start": "npm run shell", "deploy": "firebase deploy --only functions", "deploy:prod": "firebase deploy --only functions --project production", "logs": "firebase functions:log", "lint": "eslint .", "lint:fix": "eslint . --fix", - "test": "firebase emulators:exec --only firestore,auth 'npm run test:run'", + "test": "firebase emulators:exec --project demo-functions-test --only firestore,auth 'npm run test:run'", "test:run": "jest" }, "engines": { diff --git a/package.json b/package.json index 0d142ab0..0c243318 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,10 @@ "build": "react-scripts build", "predeploy": "npm run build", "deploy": "gh-pages -d build", + "emulators": "firebase emulators:start --project demo-mprc-local --only auth,firestore,functions", "lint:fix": "eslint --fix --ext .js,.jsx .", "test": "react-scripts test", + "test:spa-navigation": "node --test tests/spa-navigation.test.js", "test:rules": "firebase emulators:exec --only firestore --project demo-rules-test \"npx --no-install jest --config tests/firestore-rules/jest.config.js\"", "test:rules:watch": "firebase emulators:exec --only firestore --project demo-rules-test \"npx --no-install jest --config tests/firestore-rules/jest.config.js --watch\"", "eject": "react-scripts eject", diff --git a/public/404.html b/public/404.html index 560606f1..45218775 100644 --- a/public/404.html +++ b/public/404.html @@ -3,12 +3,7 @@